Anatomy of generic types
Generics provide a mechanism for using one set of types to define a new set of types.
In your example, you can define a generic type for keepers, like so:
class Keeper<Animal> {}
This definition immediately defines all the corresponding keeper types, as desired:
You can verify these types are real by creating values of them, specifying the entire type in the initializer:
var aCatKeeper = Keeper<Cat>()
What’s going on here? First, Keeper is the name of a generic type.
But you might say that a generic type isn’t really a type at all. It’s more like a recipe for making real types, or concrete types. One sign of this is the error you get if you try to instantiate it in isolation:
var aKeeper = Keeper() // compile-time error!
The compiler complains here because it doesn’t know what kind of keeper you want. That Animal in angle brackets is the type parameter that specifies the type for the kind of animal you’re keeping.
Once you provide the required type parameter, as in Keeper<Cat>, the generic Keeper becomes a new concrete type. Keeper<Cat> is different from Keeper<Dog>, even though they started from the same generic type; these resulting concrete types are called specializations of the generic type.
To summarize the mechanics, in order to define a generic type like Keeper<Animal> you only need to choose the name of the generic type and of the type parameter. The name of the type parameter should clarify the relationship between the type parameter and the generic type. You’ll encounter meaningless names like T from
time to time, but these names should be avoided where possible.
In one stroke, the generic type Keeper<Animal> defines a set of new types. Those are all the specializations of Keeper<Animal> implied by all possible concrete types that one could substitute for the type parameter Animal.
Notice that the type Keeper doesn’t currently store anything at all, or even use the type Animal in any way. Essentially, generics are a way to systematically define sets of types.