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.
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.
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:
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>
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>
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.
This is one of the best Explanations I’ve seen about Kotlin Generics in a While 👍. It’s easier to understand. Thank You!!
Glad you liked it!