Open In App

Kotlin generics

Last Updated : 08 Jun, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

Generics are one of Kotlin's most powerful features. They allow us to write flexible, reusable, and type-safe code. With generics, we can define classes, methods, and properties that work with different types while still maintaining compile-time type safety.

What Are Generics?

A generic type is a class or method that is parameterized by a type. This means we can use it with different data types without rewriting the same logic for each type. To declare a generic class or method in Kotlin, we use angle brackets < > to specify the type parameter.

Example:

class MyClass<T>(text: T) {
var name = text
}

Here, T is a type parameter. It can be replaced with any type (e.g., String, Int, etc.) when creating an instance of MyClass.

Creating Instances

To create an instance of such a class, we need to provide the type of arguments:

val my1: MyClass<String> = MyClass<String>("GeeksforGeeks")

If the parameters can be inferred from the arguments of a constructor, one is allowed to omit the type arguments:

val my2 = MyClass("GeeksforGeeks")  // Inferred as MyClass<String>

Here, GeeksforGeeks has type String, so the compiler figures out that we are talking about MyClass<String> 

Advantages of Generics

  1. Eliminates Type Casting- We don’t need to manually cast types.
  2. Ensures Type Safety- Generics allow only one type per instance, reducing errors.
  3. Compile-Time Checking- Type-related mistakes are caught at compile time, not at runtime.

Without Generics

In the below example, we create an Company class with a primary constructor having a single parameter. Now, we try to pass the different types of data in the object of Company class as String and Integer. The primary constructor of Company class accepts string type ("GeeksforGeeks") but gives compile time error when passes Integer type (12). 

Kotlin program without generic class:

Kotlin
class Company(name: String) {
    var companyName = name
}

fun main(){
    val c = Company(12) // compile time error
}

Output:

Error: The integer literal does not conform to the expected type String

With Generics

In order to solve the above problem, we can create a generic type class that is user defined accepts the different types of parameters in a single class. The class Company of type is a general type class that accepts both Int and String types of parameters. 

Kotlin program using the generic class:

Kotlin
class Company<T>(name: T) {
    var companyName = name
}

fun main() {
    val c1 = Company("GeeksforGeeks")
    val c2 = Company(1234)
    
    println(c1.companyName)
    println(c2.companyName)
}

Output:

GeeksforGeeks
1234

Variance in Kotlin

In Kotlin, generics are invariant by default. This means that a generic class like List<Any> is not a supertype of List<String>, even though String is a subtype of Any. To handle this, Kotlin provides two special keywords:

  1. out (for covariance)
  2. in (for contravariance)

Kotlin out and in Keywords

The out Keyword - In Kotlin, we can use the out keyword on the generic type which means we can assign this reference to any of its supertypes. The out value can only produced by the given class but can not consumed:

class OutClass<out T>(val value: T) {
fun get(): T = value
}

Above, we have defined an OutClass class that can produce a value of type T. Then, we can assign an instance of the OutClass to the reference that is a supertype of it:

val out = OutClass("string")
val ref: OutClass<Any> = out // Allowed because of out

Note: If we have not used the out type in the above class, then given statement will produce a compiler error. 

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

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

Here, we have declared a toString() method that only be consuming a value of type T. Then, we can assign a reference of type Number to the reference of its subtype – Int:

val inClassObject: InClass<Number> = InClass()
val ref: InClass<Int> = inClassObject // Allowed because of in

Note: If we have not used the in type in the above class, then the given statement will produce a compiler error.

Covariance using out:

Covariance implies substituting subtypes is acceptable, but supertypes is not, i.e. the generic function/class may accept subtypes of the datatype it is already defined for, e.g. a generic class defined for Number can accept Int, but a generic class defined for Int cannot accept Number. This can be implemented in Kotlin using the out keyword as follows- 

Kotlin
fun main() {
    val x: MyClass<Any> = MyClass<Int>()     // Error: Type mismatch
    val y: MyClass<out Any> = MyClass<String>() // Works since String is a subtype of Any
    val z: MyClass<out String> = MyClass<Any>() // Error since Any is a supertype of String
}

class MyClass<T>


We can directly allow covariance by appending out keyword to the declaration site. The following code works just fine.

Kotlin
fun main() {
        val y: MyClass<Any> = MyClass<String>() // Compiles without error
}

class MyClass<out T>

Contravariance

It is used to substitute a supertype value in the subtypes, i.e. the generic function/class may accept supertypes of the datatype it is already defined for, e.g. a generic class defined for Number cannot accept Int, but a generic class defined for Int can accept Number. It is implemented in Kotlin using the in keyword as follows- 

Kotlin
fun main(args: Array<String>) {
        var a: Container<Dog> = Container<Animal>() //compiles without error
        var b: Container<Animal> = Container<Dog>() //gives compilation error
}
open class Animal
class Dog : Animal()
class Container<in T>

Type projections

If we want to copy all the elements of an array of some type into the array of Any type then it can be possible, but to allow the compiler to compile our code we need to annotate the input parameter with the out keyword. This makes the compiler to infer that input argument can be of any type that is a subtype of the Any: 

Kotlin program of copying elements of one array into another:

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

val fromArray = arrayOf(1, 2, 3)
val toArray = arrayOfNulls<Any>(3)

copy(fromArray, toArray)
toArray.forEach { println(it) }

Output:

1
2
3

Star projections (*)

When we do not know about the specific type of the value and we just want to print all the elements of an array then we use star(*) projection. 

Kotlin program of using star projections:

Kotlin
fun printAll(elements: Array<*>) {
    for (element in elements) {
        println(element)
    }
}

val array = arrayOf("GeeksforGeeks", "Kotlin", "Generics")
printAll(array)

Output:

GeeksforGeeks
Kotlin
Generics

Next Article
Article Tags :

Similar Reads