Kotlin generics

Generics are the powerful features that allow us to define classes, methods and properties which are accessible using different data types while keeping a check of the compile-time type safety.

Creating parameterized classes –
A generic type is a class or method that is parameterized over types. We always use angle brackets (<>) to specify the type parameter in the program.

Generic class is defined as follows:

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

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

val my : MyClass<String> = Myclass<String>("GeeksforGeeks")

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

val my = MyClass("GeeksforGeeks") 

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

Advantages of generic –

  1. Type casting is evitable- No need to typecast the object.
  2. Type safety- Generic allows only single type of object at a time.
  3. Compile time safety- Generics code is checked at compile time for the parameterized type so that it avoids run time error.

Generic use in our program-

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

Kotlin program without generic class-

filter_none

edit
close

play_arrow

link
brightness_4
code

class Company (text: String) {
    var x = text
    init{
        println(x)
    }
}
fun main(args: Array<String>){
    var name: Company = Company("GeeksforGeeks")
    var rank: Company = Company(12)// compile time error
}

chevron_right


Output:

Error:(10, 33) Kotlin: The integer literal does not conform to the expected type String

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

Kotlin program using generic class-

filter_none

edit
close

play_arrow

link
brightness_4
code

class Company<T> (text : T){
    var x = text
    init{
        println(x)
    }
}
fun main(args: Array<String>){
    var name: Company<String> = Company<String>("GeeksforGeeks")
    var rank: Company<Int> = Company<Int>(12)

chevron_right


Output:

GeeksforGeeks
1234

Variance –

Unlike Java, Kotlin makes arrays invariant by default. By extension, generic types are invariant in Kotlin. This can be managed by the out and in keywords. Invariance is the property by which a standard generic function/class already defined for a particular data type, cannot accept or return another datatype. Any is the supertype of all other datatypes.
Variance is of two types-

  1. Declaration-site variance(using in and out)
  2. Use-site variance: Type projection

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 {
        return 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    

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 {
    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 in = InClass()
val ref: InClass = in

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

Covariance –

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-

filter_none

edit
close

play_arrow

link
brightness_4
code

fun main(args: Array<String>) {
    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>

chevron_right


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

filter_none

edit
close

play_arrow

link
brightness_4
code

fun main(args: Array<String>) {
        val y: MyClass<Any> = MyClass<String>() // Compiles without error
}
class MyClass<out T>

chevron_right


Contracovariance –

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-

filter_none

edit
close

play_arrow

link
brightness_4
code

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>

chevron_right


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 –

filter_none

edit
close

play_arrow

link
brightness_4
code

fun copy(from: Array<out Any>, to: Array<Any>) {
    assert(from.size == to.size)
    // copying (from) array to (to) array
    for (i in from.indices)
        to[i] = from[i]
    // printing elements of array in which copied
    for (i in to.indices) {
    println(to[i])
    }
}
fun main(args :Array<String>) {
    val ints: Array<Int> = arrayOf(1, 2, 3)
    val any :Array<Any> = Array<Any>(3) { "" }
    copy(ints, any)
  
}

chevron_right


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 –

filter_none

edit
close

play_arrow

link
brightness_4
code

// star projection in array
fun printArray(array: Array<*>) {
    array.forEach { print(it) }
}
fun main(args :Array<String>) {
    val name  = arrayOf("Geeks","for","Geeks")
    printArray(name)
}

chevron_right


Output:

GeeksforGeeks


My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.




Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.