invariant
// Java
interface Collection<E> ... {
void addAll(Collection<E> items);
}
void copyAll(Collection<Object> to, Collection<String> from) {
to.addAll(from); // !!! Would not compile with the naive declaration of addAll
}
Collection<String>
is not a subtype of Collection<Object>
covariant
Java: Use-site variance; Kotlin: Type projections
// Java
interface Collection<E> ... {
void addAll(Collection<? extends E> items);
}
void copyAll(Collection<Object> to, Collection<String> from) {
to.addAll(from); // OK!
}
assign an instance of Collection<String>
to Collection<? extends Object>
read E
from Producer Collection<? extends E> items
// Kotlin
class Array<T>(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
}
fun copy(from: Array<out Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any)
assign an instance of Array<Int>
to Array<out Any>
from: Array<out Any>
only with get function
Declaration-site variance
// Kotlin
abstract class Source<out T> {
abstract fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}
assign an instance of Source<String>
to Source<Any>
objects: Source<Any>
Producer out Any
contravariant
Java: Use-site variance; Kotlin: Type projections
// Java
List<Animal> animals = new ArrayList<>();
List<? super Cat> cats = animals;
cats.add(new Cat());
assign an instance of List<Animal>
to List<? super Cat>
write Cat
to Consumer List<? super Cat> cats
// Kotlin
class Array<T>(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
}
fun fill(dest: Array<in String>, value: String) {
// ...
}
assign an instance of Array<CharSequence>
or Array<Any>
to Array<in String>
dest: Array<in String>
only with set function
Declaration-site variance
// Kotlin
abstract class Comparable<in T> {
abstract 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!
}
assign an instance of Comparable<Number>
to Comparable<Double>
y: Comparable<Double>
Consumer in Double
Declaration-site to Use-site variance
interface Comparator<in T> {
int compare(T o1, T o2);
}
interface Foo {
Comparator<Integer> getComparator();
}
in T
-> contravariant
upper Comparator<Integer>
-> lower Comparator<? super Integer>
interface Comparator<T> {
int compare(T o1, T o2);
}
interface Foo {
Comparator<? super Integer> getComparator();
}
Use-site to Declaration-site variance
interface Baz<T, U> {
Supplier<? extends U> baz(Consumer<? super T> consumer);
}
lower Consumer<? super T>
-> upper Consumer<T>
covariant -> out T
upper Supplier<? extends U>
-> lower Supplier<U>
contravariant -> in U
interface Baz<out T, in U> {
Supplier<U> baz(Consumer<T> consumer);
}
Array Java covariant
Apple[] apples = new Apple[10];
Fruit[] fruits = apples;
fruits[0] = new Orange(); // throws an exception at runtime (ArrayStoreException or ArrayTypeMismatchException, respectively)
Apple apple = apples[0];
Kotlin invariant ensures compile time safety and prevents runtime errors
ref.
Recommended Posts