Variance Svetlana Isakova
Variance Describes how types with the same base type and different type arguments relate to each other Animal Producer<Animal> Consumer<Animal> Cat Producer<Cat> Consumer<Cat>
Example: Java arrays Is it possible in Java to pass String[] as an argument for Object[] parameter? yes Object[] Is it safe? no String[]
java.lang.arraystoreexception String[] strings = foo(strings); new String[] { "a", "b", "c" ; private static void foo(object[] array) { /*... */
java.lang.arraystoreexception String[] strings = foo(strings); new String[] { "a", "b", "c" ; private static void foo(object[] array) { array[1] = 42; java.lang.arraystoreexception: java.lang.integer
Java: lists are safe List<String> strings = Arrays.asList("a", "b", "c"); foo(strings); Error: incompatible types: java.util.list<java.lang.string> cannot be converted to java.util.list<java.lang.object> private static void foo(list<object> list) { list.set(0, 42);
Definitions
Classes & types Classes Types String String String? List List<Int> List<String?> List<List<String>>
Subtyping A type B is a subtype of a type A if you can use the value of the type B whenever a value of the type A is required. A Number Int String B Int Int Int
The compiler always checks subtyping fun test(i: Int) { val n: Number = i fun f(s: String) { /*...*/ f(i) Error: inferred type is Int but String was expected
Subtyping for nullable types val s: String = "abc" val t: String? = s A? Int? Int A Int Int?
Subtyping for generic types interface Foo<T> { A Foo<A>? B Foo<B>
Declaration-site variance
Invariant classes A generic class Foo is called invariant on the type parameter if, for any two different types A and B, Foo<A> is not a subtype or a supertype of Foo<B> A Foo<A> Foo<B> B Foo<B> Foo<A>
Covariance A generic class Producer<T> is covariant on its type parameter if the following holds: Producer<B> is a subtype of Producer<A> when B is a subtype of A. A Producer<A> B Producer<B>
List & MutableList Any MutableList<Any> List<Any> String MutableList<String> List<String>
Kotlin: mutable lists val list = mutablelistof("a", "b", "c") foo(list) Error: Type mismatch: inferred type is MutableList<String> but MutableList<Any> was expected fun foo(list: MutableList<Any>) { list += 42
Kotlin: read-only lists val list = listof("a", "b", "c") foo(list) fun foo(list: List<Any>) { list += 42
Declaration-site variance
in / out positions interface Transformer<T, R> { fun transform(t: T): R in position out position
<out T> means covariant interface Producer<out T> { fun produce(): T T must be used Producer<Any> only in out positions Producer<Int>
Why is it safe? interface Producer<out T> { fun produce(): T val producer: Producer<Int> foo(producer) fun foo(anyproducer: Producer<Any>) { val any = anyproducer.produce()
<T> means invariant interface Foo<T> { fun bar(): T fun baz(t: T) Foo can t be declared as covariant, because T is used both in out and in positions
List<out T> interface List<out T> : Collection<T> { operator fun get(index: Int): T fun sublist(fromindex: Int, //... toindex: Int): List<T> interface MutableList<T> : List<T>, MutableCollection<T> { override fun add(element: T): Boolean
@UnsafeVariance interface List<out T> : Collection<T> { operator fun get(index: Int): T override fun contains( element: @UnsafeVariance T): Boolean //...
Contravariance
Contravariance A generic class Consumer<T> is contravariant on its type parameter if the following holds: Consumer<A> is a subtype of Consumer<B> when B is a subtype of A. A Consumer<A> B Consumer<B>
<in T> means contravariant interface Consumer<in T> { fun consume(t: T) T must be used Consumer<Any> only in in positions Consumer<Int>
Example: comparator interface Comparator<in T> { fun compare(o1: T, o2: T): Int Any Comparator<Any> Int Comparator<Int>
Using comparator val anycomparator: Comparator<Any> = Comparator { a, b -> a.hashcode() - b.hashcode() fun foo(strcomparator: Comparator<String>) { listof("a", "c", b"). sortedwith(strcomparator)
Covariant, contravariant & invariant classes Covariant Contravariant Invariant Producer<out T> Consumer<in T> Foo<T> T only in "out" positions T only in "in" positions T in any position Producer<Any> Producer<Int> Consumer<Any> Consumer<Int> Foo<Any> Foo<Int>
(T) -> R interface Function1<in P, out R> { operator fun invoke(p: P): R Cat (Cat) -> Number Number Animal (Animal) -> Int Int
Use-site variance
Java wildcards public interface Stream<T> { <R> Stream<R> map( Function<? super T,? extends R> mapper);
copydata example fun <T> copydata( source: MutableList<T>, destination: MutableList<T>) { for (item in source) { destination.add(item) val ints = mutablelistof(1, 2, 3) val anyitems = mutablelistof<any>() copydata(ints, anyitems) Error: Cannot infer type parameter T
First solution: one more type parameter fun <T : R, R> copydata( source: MutableList<T>, destination: MutableList<R>) { for (item in source) { destination.add(item) val ints = mutablelistof(1, 2, 3) val anyitems = mutablelistof<any>() copydata(ints, anyitems)
Another solution: MutableList<out T> fun <T> copydata( source: MutableList<out T>, destination: MutableList<T>) { for (item in source) { destination.add(item)
Projected type val list: MutableList<out Number> = list.add(42) Error: Out-projected type 'MutableList<out Number>' prohibits the use of 'fun add(element: E): Boolean'
MutableList<in T> fun <T> copydata( source: MutableList<T>, destination: MutableList<in T>) { for (item in source) { destination.add(item)
Star projection MutableList<*> MutableList<Any?> List<*> = List<out Any?> = List<Any?>
Using List<*> fun printfirst(list: List<*>) { if (list.isnotempty()) { println(list.first()) returns Any?
Star projection MutableList<*> MutableList<Any?> MutableList<*> = MutableList<out Any?> = Comparator<*> Comparator<in Nothing>???>
Thank you!