Upload
cs-center
View
140
Download
6
Embed Size (px)
Citation preview
Variance
Svetlana Isakova
Producer<Cat>
Producer<Animal>
Cat
Animal
Consumer<Cat>
Consumer<Animal>
VarianceDescribes how types with the same base type and different type arguments relate to each other
Example: Java arrays
• yes
String[]
Object[]
• Is it possible in Java to pass String[] as an argument for Object[] parameter?
• Is it safe?
• no
java.lang.ArrayStoreException
String[] strings = new String[] { "a", "b", "c" };foo(strings);
private static void foo(Object[] array) {
/* ... */
}
java.lang.ArrayStoreException
String[] strings = new String[] { "a", "b", "c" };foo(strings);
private static void foo(Object[] array) { array[1] = 42; } java.lang.ArrayStoreException: java.lang.Integer
Java: lists are safeList<String> strings = Arrays.asList("a", "b", "c");foo(strings);
private static void foo(List<Object> list) { list.set(0, 42); }
Error: incompatible types: java.util.List<java.lang.String> cannot be converted to java.util.List<java.lang.Object>
Definitions
Classes & types
Classes Types
String String String?
ListList<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.
Int
Number
B
A
Int
String
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 typesval s: String = "abc"val t: String? = s
A
A?
Int?
Int
Int
Int?
✗
Subtyping for generic types
Foo<B>
Foo<A>
B
A
?
interface Foo<T> {}
Declaration-site variance
Invariant classesA 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>
Foo<A>
Foo<B>
✗Foo<B>
Foo<A>
✗B
A
CovarianceA 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.
Producer<B>
Producer<A>
B
A
List & MutableList
MutableList<String>
MutableList<Any>
String
Any
✗List<String>
List<Any>
Kotlin: mutable lists
val list = mutableListOf("a", "b", "c") foo(list)
fun foo(list: MutableList<Any>) { list += 42 }
Error: Type mismatch: inferred type is MutableList<String> but MutableList<Any> was expected
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 covariantinterface Producer<out T> { fun produce(): T }
T must be used only in “out” positions
Producer<Int>
Producer<Any>
Why is it safe?
val producer: Producer<Int>foo(producer)
fun foo(anyProducer: Producer<Any>) { val any = anyProducer.produce()}
interface Producer<out T> { fun produce(): T }
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
<T> means invariant
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
ContravarianceA 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.
Consumer<B>
Consumer<A>
B
A
<in T> means contravariantinterface Consumer<in T> { fun consume(t: T) }
T must be used only in “in” positions
Consumer<Int>
Consumer<Any>
Example: comparator
Comparator<Int>
Comparator<Any>
Int
Any
interface Comparator<in T> { fun compare(o1: T, o2: T): Int}
Using comparatorval 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<Int>
Producer<Any> Consumer<Any>
Consumer<Int>
Foo<Any>
Foo<Int>✗
(T) -> Rinterface Function1<in P, out R> { operator fun invoke(p: P): R }
(Animal) -> Int
(Cat) -> Number
Animal
Cat
Int
Number
Use-site variance
Java wildcards
public interface Stream<T> { <R> Stream<R> map( Function<? super T, ? extends R> mapper);}
copyData examplefun <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>' prohibitsthe 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 ???>Comparator<in Nothing>
Thank you!