Java
Kotlin

[Java, Kotlin] Type Variance

More than 1 year has passed since last update.

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

  • lower to upper
  • producer

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

  • upper to lower
  • consumer

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.