The new Certification Class of REST With Spring is out:

>> CHECK OUT THE COURSE

1. Overview

In this article, we will be looking at the generic types in the Kotlin language.

They are very similar to those from the Java language, but the Kotlin language creators tried to make them a little bit more intuitive and understandable by introducing special keywords like out and in.

2. Creating Parameterized Classes

Let’s say that we want to create a parameterized class. We can easily do this in Kotlin language by using generic types:

class ParameterizedClass<A>(private val value: A) {

    fun getValue(): A {
        return value
    }
}

We can create an instance of such a class by setting a parameterized type explicitly when using the constructor:

val parameterizedClass = ParameterizedClass<String>("string-value")

val res = parameterizedClass.getValue()

assertTrue(res is String)

Happily, Kotlin can infer the generic type from the parameter type so we can omit that when using the constructor:

val parameterizedClass = ParameterizedClass("string-value")

val res = parameterizedClass.getValue()

assertTrue(res is String)

3. Kotlin out and in Keywords

3.1. The out Keyword

Let’s say that we want to create a producer class that will be producing a result of some type T. Sometimes; we want to assign that produced value to a reference that is of a supertype of the type T.

To achieve that using Kotlin, we need to use the out keyword on the generic type. It means that we can assign this reference to any of its supertypes. The out value can be only be produced by the given class but not consumed:

class ParameterizedProducer<out T>(private val value: T) {
    fun get(): T {
        return value
    }
}

We defined a ParameterizedProducer class that can produce a value of type T.

Next; we can assign an instance of the ParameterizedProducer class to the reference that is a supertype of it:

val parameterizedProducer = ParameterizedProducer("string")

val ref: ParameterizedProducer<Any> = parameterizedProducer

assertTrue(ref is ParameterizedProducer<Any>)

If the type T in the ParamaterizedProducer class will not be the out type, the given statement will produce a compiler error.

3.2. The in Keyword

Sometimes, we have an opposite situation meaning that we have a reference of type T and we want to be able to assign it to the subtype of T.

We can use the in keyword on the generic type if we want to assign it to the reference of its subtype. The in keyword can be used only on the parameter type that is consumed, not produced:

class ParameterizedConsumer<in T> {
    fun toString(value: T): String {
        return value.toString()
    }
}

We declare that a toString() method will only be consuming a value of type T.

Next, we can assign a reference of type Number to the reference of its subtype – Double:

val parameterizedConsumer = ParameterizedConsumer<Number>()

val ref: ParameterizedConsumer<Double> = parameterizedConsumer

assertTrue(ref is ParameterizedConsumer<Double>)

If the type T in the ParameterizedCounsumer will not be the in type, the given statement will produce a compiler error.

4. Type Projections

4.1. Copy an Array of Subtypes to an Array of Supertypes

Let’s say that we have an array of some type, and we want to copy the whole array into the array of Any type. It is a valid operation, but to allow the compiler to compile our code we need to annotate the input parameter with the out keyword.

This lets the compiler know that input argument can be of any type that is a subtype of the Any:

fun copy(from: Array<out Any>, to: Array<Any?>) {
    assert(from.size == to.size)
    for (i in from.indices)
        to[i] = from[i]
}

If the from parameter is not of the out Any type, we will not be able to pass an array of an Int type as an argument:

val ints: Array<Int> = arrayOf(1, 2, 3)
val any: Array<Any?> = arrayOfNulls(3)

copy(ints, any)

assertEquals(any[0], 1)
assertEquals(any[1], 2)
assertEquals(any[2], 3)

4.2. Adding Elements of a Subtype to an Array of its Supertype

Let’s say that we have the following situation – we have an array of Any type that is a supertype of Int and we want to add an Int element to this array. We need to use the in keyword as a type of the destination array to let the compiler know that we can copy the Int value to this array:

fun fill(dest: Array<in Int>, value: Int) {
    dest[0] = value
}

Then, we can copy a value of the Int type to the array of Any:

val objects: Array<Any?> = arrayOfNulls(1)

fill(objects, 1)

assertEquals(objects[0], 1)

4.3. Star Projections

There are situations when we do not care about the specific type of the value. Let’s say that we just want to print all the elements of an array and it does not matter what the type of the elements in this array is.

To achieve that, we can use a star projection:

fun printArray(array: Array<*>) { 
    array.forEach { println(it) }
}

Then, we can pass an array of any type to the printArray() method:

val array = arrayOf(1,2,3) 
printArray(array)

When using the star projection reference type, we can read values from it, but we cannot write them because it will cause a compilation error.

5. Generic Constraints

Let’s say that we want to sort an array of elements, and each element type should implement a Comparable interface. We can use the generic constraints to specify that requirement:

fun <T: Comparable<T>> sort(list: List<T>): List<T> {
    return list.sorted()
}

In the given example, we defined that all elements T needed to implement the Comparable interface. Otherwise, if we will try to pass a list of elements that does not implement this interface, it will cause a compiler error.

We defined a sort function that takes as an argument a list of elements that implement Comparable, so we can call the sorted() method on it. Let’s look at the test case for that method:

val listOfInts = listOf(5,2,3,4,1)

val sorted = sort(listOfInts)

assertEquals(sorted, listOf(1,2,3,4,5))

We can easily pass a list of Ints because the Int type implements the Comparable interface.

6. Conclusion

In this article, we were looking at the Kotlin Generic types. We saw how to use the out and in keywords properly. We used type projections and defined a generic method that uses generic constraints.

The implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.

Go deeper into building a REST API with Spring:

>> CHECK OUT THE COURSE