Kotlin Generics

Filed Under: Kotlin

In this tutorial, we’ll be looking in Kotlin Generics and Variances.

Generics

Generics are a powerful feature that allows us to define a common class/method/property that can be operated using different types while keeping a check of the compile-time type safety.
They are commonly used in Collections.
A generic type is a class or interface that is parameterized over types. We use angle brackets (<>) to specify the type parameter. To understand Generics, we need to understand types.
Every class has a type which is generally the same as the class.
Though, in Kotlin a nullable type such as String? won’t be considered a class type.
Same goes for a List<String> etc.

An example of Kotlin Generic classes is given below:


fun main(args: Array<String>) {
    var a = A("")
    var b: A<Int> = A(1) //explicit
}

class A<T>(argument: T) {
    var result = argument
}

We’ve instantiated the class using explicit types as well as allowing the compiler to infer them.

Variance

Variance is all about substituting a type with subtypes or supertypes.
The following thing works in Java:


Number[] n = newNumber[2];
n[0] = newInteger(1);
n[1] = newDouble(47.24);

This means Arrays in Java are covariant.

Convariant means, substituting:
Subtypes are acceptable.
Supertypes are not.

So using the covariant principle the following Java code works as well:


Integer[] integerArray = {1,2,3};
Number[] numberArray = integerArray;

The Number class is the parent of Integer class hence the inheritance and subtyping principle works above.
But the above assignment is risky if we do something like this:


numberArray[1] = 4.56 //compiles successfully

This would lead to a runtime exception in Java since we cannot store a Double as an Int.

Kotlin Arrays stays away from this principle by making arrays invariant by default.

Invariant means, substituting:
Subtypes are not allowed.
Supertypes are not allowed.

So the above runtime error won’t occur with Kotlin Arrays.


val i = arrayOf(1, 2, 3)
val j: Array<Any> = i //this won't compile.

Hence, one of the major differences in variances between Kotlin and Java is:

Kotlin Arrays are invariant. Java Arrays are covariant.

Applying the above concepts DON’T COMPILE when used with Generics and Collections for both Java and Kotlin.


import java.util.ArrayList;
import java.util.List;

public class Hi {

    public static void main(String[] args) {
        List<String> stringList= new ArrayList<>();
        List<Object> myObjects = stringList; //compiler error
    }

}

By default, the Generic types are invariant. In Java, we use wildcard characters to use the different type of variances.
There are two major types of variances besides invariant.

  • Covariant
  • Contravariant

Covariant

A ? extends Object is a wildcard argument which makes the type as covariant.
Our previous java code now works fine.




public class Hi<T> {


    public static void main(String[] args) {


        Hi<Integer> j = new Hi();
        Hi<Object> n = j; //compiler error
        Hi<? extends Object> k = j; //this works fine

    }

}

The third statement is an example of Covariance using wild card arguments.

The modifier out is used for applying covariance in Kotlin.


fun main(args: Array<String>) {


    val x: A<Any> = A<Int>() // Error: Type mismatch
    val y: A<out Any> = A<String>() // Works
    val z: A<out String> = A<Any>() // Error
}
class A<T>

In Kotlin, we can directly annotate the wildcard argument on the parameter type of the class. This is known as declaration-site variance.


fun main(args: Array<String>) {
    val x: A<Any> = A<Int>()
}

class A<out T>

The above Kotlin code looks more readable than the Java one.


fun main(args: Array<String>) {
    var correct: Container<Vehicle> = Container<Car>()
    var wrong: Container<Car> = Container<Vehicle>()
}


open class Vehicle
class Car : Vehicle()
class Container<out T>
To sum up Covariance in Kotlin:
Use out
Used to set substitute subtypes. Not the other way round

Contraconvariant

This is just the opposite of Covariance. It’s used to substitute a supertype value in the subtypes.
It cannot work the other way round.
It uses the in modifier


fun main(args: Array<String>) {
    var correct: Container<Car> = Container<Vehicle>()
    var wrong: Container<Vehicle> = Container<Car>()
}

open class Vehicle

class Car : Vehicle()

class Container<in T>

Kotlin in is equivalent to <? super T> of Java.
Kotlin out is equivalent to <? extends T> of Java.

This brings an end to this tutorial on Kotlin Generics and Variance.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages