Generic classes
As in Java, classes in Kotlin may have type parameters:
class Box<T>(t : T) { var value = t }In general, to create an instance of such a class, one needs to provide the type parameters:
val box : Box<Int> = Box<Int>(1)But if the parameters may be inferred, e.g. from the constructor arguments or by some other means, one is allowed to omit the type parameters:
val box = Box(1) // 1 has type Int, so the compiler figures out that we are talking about Box<Int>Generic types are retained at runtime
Unlike Java, the information about actual type arguments of Kotlin classes is retained at runtime. This allows for checking complete types of objects at runtime:
if (b is Box<Int>) { // 'is' replaces Java's 'instanceof' // b is guaranteed to be Box<Int>, not just Box }We can even use type variables in is-checks:
if (foo is T) { // this is a non-trivial check // ... }Also, we can create generic arrays:
class ArrayDemo<T> { val array = Array<T>() // Array is just a class }And no, Kotlin does NOT have raw types, and type erasure does not play that much of a role. For example, there can not be a values of type Box, without type arguments, if Box is a generic class. This corresponds to Effective Java Item 23: Don't use raw types in new code and Item 24: Eliminate unchecked warnings.
| Run-time generics are not implemented yet |
Variance
One of the most tricky parts of Java's type system is wildcard types (see Java Generics FAQ). And Kotlin doesn't have any. Instead, it has two other things: declaration-site variance and type projections.
First, let's think about why Java needs those mysterious wildcards. The problem is explained in Effective Java, Item 28: Use bounded wildcards to increase API flexibility. First, generic types in Java are invariant, meaning that List<String> is not a subtype of List<Object>. Why so? If List was not invariant, it would have been no better than Java's arrays, cause the following code would have compiled and cause an exception at runtime:
So, Java prohibits such things in order to guarantee run-time safety. But this has some implications. For example, consider the addAll() method from Collection interface. What's the signature of this method? Intuitively, we'd put it this way:
But then, we would not be able to do the following simple thing (which is perfectly safe):
(In Java, we learned this lesson the hard way, see Effective Java's Item 25: Prefer lists to arrays.)
That's why the actual signature of addAll() is the following:
The wildcard type argument ? extends T indicates that this method accepts a collection of objects of some subtype of T, not T itself. This means that we can safely read T's from items (elements of this collection are instances of a subclass of T), but cannot write to it since we do not know what objects comply to that unknown subtype of T. In return for this limitation, we have the desired behaviour: Collection<String> is a subtype of Collection<? extends Object>. In "clever words", the wildcard with an extends-bound (upper bound) makes the type covariant.
The key to understanding why this trick works is rather simple: if you can only take items from a collection, then a collection of String's and reading Object's from it is fine. Conversely, if you can only put items into the collection, it's OK to take a collection of Object's and put String's into it: in Java we have List<? super String> a supertype of List<Object>. The latter is called contravariance, and you can only call methods that take String as an argument on List<? super String> (e.g., you can call add(String) or set(int, String)), while if you call something that returns T in List<T>, you don't get a String, but an Object.
Joshua Bloch calls those objects you only read from producers, and those you only write to – consumers. He recommends: "For maximum flexibility, use wildcard types on input parameters that represent producers or consumers", and proposes the following mnemonic:
PECS stands for producer-extends, consumer-super.
NOTE: if you use a producer-object, say, List<? extends Foo>, you are not allowed to call add() or set() on this object, but this does not mean that this object is immutable: for example, nothing prevents you to call clear() to remove all items from the list, since clear() does not take any parameters at all. The only thing guaranteed by wildcards (or other types of variance) is type safety. Immutability is a completely different story.
Declaration-site variance
Suppose we have a generic interface Source<T> that does not have any methods that take T as a parameter, only methods that return T:
Then, it would be perfectly safe to store a reference to an instance of Source<String> in a variable of type Source<Object> – there're no consumer-methods to call. But Java does not know this, and still prohibits:
To fix this, we have to declare objects of type Source<? extends Object> that is sort of meaningless, because we can call all the same methods on such a variable, as before, so there's no value added by the more complex type. But the compiler does not know that.
In Kotlin, there is a way to explain this sort of thing to the compiler. This is called declaration-site variance: we can annotate the type parameter T of Source to make sure that it is only returned (produced) from members of Source<T>, and never consumed. To do this we provide the out modifier:
abstract class Source<out T> { fun nextT() : T } fun demo(strs : Source<String>) { val objects : Source<Any> = strs // This is OK, since T is an out-parameter // ... }The general rule is: when a type parameter T of a class C is declared out, it may occur only in out-position in the members of C, but in return C<Base> can safely be a supertype of C<Derived>.
In "clever words" they say that the class C is covariant in the parameter T, or that T is a covariant type parameter. You can think of C as being a producer of T's, and NOT a consumer of T's.
The out modifier is called a variance annotation, and since it is provided at the type parameter declaration site, we talk about declaration-site variance. This is in contrast with Java's use-site variance where wildcards in the type usages make the types covariant.
In addition to out, Kotlin provides a complementary variance annotation: in. It makes a type parameter contravariant: it can only be consumed and never produced. A good example of a contravariant class is Comparable:
abstract class Comparable<in T> { fun compareTo(other : T) : Int } fun demo(x : Comparable<Number>) { x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number // Thus, we can assign x to a variable of type Comparable<Double> val y : Comparable<Double> = x // OK! }We believe that the word in and out are self-explainig (as they were successfully used in C# for quite some time already), thus the mnemonic mentioned above is not really needed, and one can rephrase it for a higher purpose:
The Existential Transformation: Consumer in, Producer out! ![]()
Use-site variance: Type projections
It is very convenient to declare a type parameter T as out and have no trouble with subtyping on the use site. Yes, it is, when the class in question can actually be restricted to only return T's, but what if it can't? A good example of this is Array:
class Array<T>(val length : Int) { fun get(index : Int) : T { /* ... */ } fun set(index : Int, value : T) { /* ... */ } }This class cannot be either co- or contravariant in T. And this imposes certain inflexibilities. Consider the following function:
fun copy(from : Array<Any>, to : Array<Any>) { assert {from.length == to.length} for (i in from.indices) to[i] = from[i] }This function is supposed to copy item from one array to another. Let's try to apply it in practice:
val ints : Array<Int> = array(1, 2, 3) val any = Array<Any>(3) copy(ints, any) // Error: expects (Array<Any>, Array<Any>)Here we run into the same familiar problem: Array<T> is invariant in T, thus neither of Array<Int> and Array<Any> is a subtype of the other. Why? Again, because copy might be doing bad things, i.e. it might attempt to write, say, a String to from, and if we actually passed an array of Int there, a ClassCastException would have been thrown sometime later...
Then, the only thing we want to ensure is that copy() does not do any bad things. We want to prohibit it to write to from, and we can:
fun copy(from : Array<out Any>, to : Array<Any>) { // ... }What has happened here is called type projection: we said that from is not simply an array, but a restricted (projected) one: we can only call those methods that return the type parameter T, in this case it means that we can only call get(). This is our approach to use-site variance, and corresponds to Java's Array<? extends Object>, but in a little simpler way.
You can project a type with in as well:
fun fill(dest : Array<in String>, value : String) { // ... }Array<in String> corresponds to Java's Array<? super String>, i.e. you can pass an array of CharSequence or an array of Object to the fill() function.
Star-projections
Sometimes you want to say that you know nothing about the type argument, but still want to use it in a safe way. The safe way here is to say that we are dealing with an out-projection (the object does not consume any values of unknown types), and that this projection is with the upper-bound of the corresponding parameter, i.e. out Any? for most cases. Kotlin provides a shortahnd syntax for this, that we call a star-projection: Foo<*> means Foo<out Bar> where Bar is the upperbound for Foo's type parameter.
Note: star-projections are very much like Java's raw types, but safe.
Generic functions
Not only classes can have type parameters. Functions can, too. Usually, one places the type parameters in angle brackets after the name of the function:
fun singletonList<T>(item : T) : List<T> { // ... }But for Extension functions it may be necessary to declare type parameters before specifying the receiver type, so Kotlin allows the alternative syntax:
fun <T> T.basicToString() : String { return typeinfo.typeinfo(this) + "@" + System.identityHashCode(this) }If type parameters are passed explicitly at the call site, they can be only specified after the name of the function:
val l = singletonList<Int>(1)Generic constraints
The set of all possible types that can be substituted for a given type parameter may be restricted by generic constraints.
Upper bounds
The most common type a constraint is an upper bound that corresponds to Java's extends keyword:
fun sort<T : Comparable<T>>(list : List<T>) { // ... }The type specified after a colon is the upper bound: only subtype of Comparable<T> may be substituted for T. For example
sort(list(1, 2, 3)) // OK. Int is a subtype of Comparable<Int> sort(list(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>The default upper bound (if none specified) is Any?. There can be not more than one upper bound specified directly inside the angle brackets. If same type parameter needs more than one upper bound, we need a separate where-clause:
fun cloneWhenGreater<T : Comparable<T>>(list : List<T>, threshold : T) : List<T> where T : Cloneable { return list when {it > threshold} map {it.clone()} }Class objects
Another type of generic constraints are class object constraints. They restrict the properties of a class object of the root class of a type being substituted for T.
Consider the following example. Suppose, we have a class Default that has a property default that holds a default value to be used for this type:
abstract class Default<T> { val default : T }For example, the class Int could extend Default in the following way:
class Int { class object : Default<Int> { override val default = 0 } // ... }Now, let's consider a function that takes a list of nullable T's, i.e. T?, and replaces all the null's with the default values:
fun replaceNullsWithDefaults<T : Any>(list : List<T?>) : List<T> { return list map { if (it == null) T.default // For now, we don't know if T's class object has such a property else it } }For this function to compile, we need to specify a type constraint that requires a class object of T to be of a subtype of Default<T>:
fun replaceNullsWithDefaults<T : Any>(list : List<T?>) : List<T> where class object T : Default<T> { // ...Now the compiler knows that T (as a class object reference) has the default property, and we can access it. This is possible because the generic types in Kotlin are retained at runtime.
| Class object bounds are not supported yet See the corresponding issue. |

26 Comments
comments.show.hideJul 20, 2011
Anonymous
Does it support higher-order polymorphism?
Jul 20, 2011
Anonymous
no
Jul 20, 2011
Anonymous
One of the biggest issues for me with generics is support for unboxed types because of overhead. Does Kotlin support generics on primitive types without boxing ?
Jul 20, 2011
Anonymous
or something like scala's @specialized?
Jul 20, 2011
Maxim Shafirov
Not for the first version, I'm afraid.
Oct 05, 2011
Anonymous
This example:
funcopy(from:Array<outAny>,to:Array<inAny>){...}Might be more instructive if it were written like this:
funcopy<T>(from:Array<outT>,to:Array<inT>){...}I assume that in this case, the compiler would infer T at the call site.
If you have:
valints:Array<Int>=array(1,2,3)valnumbers=Array<Number>(3)copy(ints,numbers)The compiler would infer T == Number.
Also, you might consider explaining completely how variance works in Kotlin without mentioning wildcards, at least as first. You could compare Kotlin's type variance with wildcards in an optional section toward the end.
I really like what you've done here--I find it a lot more intuitive than wildcards.
Jul 21, 2011
Andrey Breslav
Thanks
Jul 21, 2011
Anonymous
Are two instantiations of the same generic type truly and entirely separate? specifically:
1. Do they have the same shared state, or separate (in Java it would be statics, in Kotlin it is class objects)? If the same, then this means that type parameters cannot be used inside class objects?
2. Is it possible to inherit twice from the same generic type instantiated with different type parameters? i.e. "class Foo : List<Int>, List<String>"
Jul 21, 2011
Andrey Breslav
1. Yes, type parameters are not available in class objects
2. No
Jul 21, 2011
Anonymous
So they are not fully reified then. I think it's worth pointing this out - not because it's bad (most practical cases would be covered by what you support), but because it's different from languages with full reification, like C#.
Jul 21, 2011
Anonymous
(In plain text, w/o curly braces, this time.)
This example:
fun copy(from: Array<out Any>, to: Array<in Any>)
Might be more instructive if it were written like this:
fun copy<T>(from: Array<out T>, to: Array<in T>)
I assume that in this case, the compiler would infer T at the call site.
If you have:
val ints: Array<Int> = array(1, 2, 3)
val numbers = Array<Number>(3)
copy(ints, numbers)
The compiler would infer T == Number.
Also, you might consider explaining completely how variance works in Kotlin without mentioning wildcards, at least as first. You could compare Kotlin's type variance with wildcards in an optional section toward the end.
I really like what you've done here--I find it a lot more intuitive than wildcards.
-Bob Lee
Jul 21, 2011
Andrey Breslav
One thing with having a generic function there is that generic functions come later in the text.
And I agree that this text is a little bit too Java-oriented, we will probably fix this.
Jul 22, 2011
Anonymous
Reified generics is the most innovative idea in Kotlin.
But does it add any performance overhead? I think JVM doesn't support reified generics. Kotlin may have to maintain the type information at runtime.
Jul 22, 2011
Maxim Shafirov
Well, any generic class, like List<T> will have a reference to a TypeInfo object, so it'll occupy 4 to 8 bytes more memory. That's it
Sep 06, 2011
Anonymous
Are you saying that the List<T> class definition will be 4 to 8 bytes larger, or that every List<T> instance will be 4 to 8 bytes larger?
Sep 07, 2011
Andrey Breslav
Every instance.
Jul 22, 2011
Anonymous
I wonder how the following code would look like in Kotlin:
This code doesn't compile in Java, because the commandHandlers list is used both as a producer (line 15) and as a consumer (line 16), so the list should have been declared as both <? extends Base> as well as <? super Base>, which is not possible. In fact, it is impossible in Java to deal with this problem properly, without using @SuppressWarnings("unchecked").
Is there way to express it in Kotlin?
Jul 23, 2011
Andrey Breslav
This does not compile in Java for a different reason. The list is OK, the handler is the problem. Here's how Kotlin handles this with declaration-site variance:
abstractclassCommand{}classMyCommand():Command{}abstractclassCommandHandler<inT:Command>{funexec(command:T):Unit;}classMyCommandHandler:CommandHandler<MyCommand>{funexec(myCommand:MyCommand){}}funtest(){List<CommandHandler<Command>>commandHandlers=ArrayList<CommandHandler<Command>>();commandHandlers.add(MyCommandHandler());CommandHandler<Command>handler=commandHandlers[0];handler.exec(MyCommand());}Sep 22, 2011
Anonymous
Would type projections allow me to have something like this on an immutable collection?
classCollection<outT>{funcontains(t:inT):boolean}That is, selectively allow some methods to act as "consumers" in an otherwise "producer" class?
Sep 22, 2011
Andrey Breslav
I don't see a way in which this can work.
Dec 27, 2011
Jan Novotný
Will there be a better support for SELF generic type? It's very handy for builder pattern. Java problem is described for example here:
http://calliopesounds.blogspot.com/2010/11/having-java-generic-class-return-type.html
Dec 28, 2011
Andrey Breslav
Yes. The type is called This.
Dec 28, 2011
Jan Novotný
Cool, can you please add some example of using This generics declaration? Or did I overlook it in the article?
Dec 28, 2011
Andrey Breslav
It's not there yet, because it's not yet implemented. I'll add a description as soon as the implementation is done.
Apr 05, 2012
Clayton Wohl
I am trying the exact example of use-site variance given on this page, and it doesn't seem to work:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.CharSequence; cannot be cast to [Ljava.lang.String;
Apr 05, 2012
Svetlana Isakova
It's a bug. Mind you post an issue to our tracker http://youtrack.jetbrains.com/issues/KT? Thanks.