Kotlin Generics - in, out, where

Manuchekhr Tursunov
4 min readApr 22, 2023

--

In Kotlin, generics provide the ability to create classes, interfaces, and methods that can work with various types, without specifying the actual type until the object is created.

In

The <in> keyword in generics is used to specify a type parameter as a contravariant. Contravariant means that the type can be used as input parameters.

Here’s an example of using <in> with generics in Kotlin:

interface AnimalCare<in T> {
fun takeCareOf(animal: T)
}

class Vet : AnimalCare<Animal> {
override fun takeCareOf(animal: Animal) {
// do something
}
}

class Animal {}

class Dog : Animal {}

fun main() {
val animalCare: AnimalCare<Dog> = Vet()
animalCare.takeCareOf(Dog())
}

In the above example, AnimalCare is a generic interface with the type parameter T marked as in. The Vet class implements the AnimalCare interface with the type parameter Animal. The main function creates an instance of AnimalCare with the type parameter Dog, but assigns it to a variable of type AnimalCare<Animal>. Since Dog is a subtype of Animal, it can be passed to the takeCareOf method of the Vet class.

Using the <in> keyword in generics allows for a greater degree of flexibility in using classes, interfaces, and methods with different types. It is especially useful when dealing with classes that accept input parameters, such as with the takeCareOf method in the above example.

Out

In Kotlin, the <out> keyword is used to define covariance in generic classes and interfaces. It allows for a more flexible use of generics, allowing a subtype of a generic type to be used in place of the parent type.

When a generic class or interface is defined with the <out> keyword, it means that the type parameter can only appear in the output positions of member functions. This means that it can only be returned from functions or used as a property type.

Here’s an example of a generic interface using the <out> keyword:

interface Producer<out T> {
fun produce(): T
}

In this example, T is a covariant type parameter. This interface has a single function produce() that returns an object of type T. Since T is covariant, the produce() function can return a subtype of T.

Here’s an example of how this interface can be used:

class FruitProducer : Producer<Fruit> {
override fun produce(): Fruit {
return Apple()
}
}

In this example, Fruit is the type parameter for Producer. The FruitProducer class implements the Producer interface and overrides the produce() function to return an object of type Fruit, which in this case is an instance of Apple. Since T is covariant, FruitProducer is allowed to return an Apple object instead of a Fruit object.

Using the <out> keyword in generics can make your code more flexible and reusable. It allows you to use subtypes of a generic type where the parent type is expected, making your code more adaptable to changes in the future.

Where

The where keyword in Kotlin is used to add constraints on the types used as generic type parameters. It allows you to specify that a generic type parameter must be a subtype of a specific class or implement a specific interface.

Here’s an example of using where to add constraints on a generic function:

fun <T> addNumbers(a: T, b: T) where T : Number {
val result = a.toDouble() + b.toDouble()
println("Result: $result")
}

In the above example, we’ve added a constraint on the generic type parameter T using the where keyword. The constraint specifies that T must be a subtype of the Number class.

Now, let’s see how we can use this function:

addNumbers(3, 5) // Output: Result: 8.0
addNumbers(2.5, 4.5) // Output: Result: 7.0
addNumbers("hello", "world") // Compilation error: Type parameter bound for 'T' is not satisfied

As you can see, the function can only be called with arguments that are subtypes of Number.

You can also use the where keyword to add multiple constraints on a generic type parameter. Here's an example:

fun <T> processList(list: List<T>) where T : CharSequence, T : Comparable<T> {
val sortedList = list.sorted()
sortedList.forEach { println(it) }
}

In the above example, we’ve added two constraints on the generic type parameter T using the where keyword. The constraints specify that T must be a subtype of the CharSequence class and must also implement the Comparable interface.

Now, let’s see how we can use this function:

val stringList = listOf("apple", "banana", "orange")
processList(stringList) // Output: apple banana orange

val intList = listOf(3, 2, 1)
processList(intList) // Compilation error: Type parameter bound for 'T' is not satisfied

As you can see, the function can only be called with arguments that are subtypes of CharSequence and implement the Comparable interface.

In summary, the where keyword in Kotlin allows you to add constraints on generic type parameters to restrict the types that can be used as arguments for generic functions or classes.

--

--