Open In App

Kotlin generics

Improve
Improve
Like Article
Like
Save
Share
Report

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 on 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 of arguments:

val my : 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 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 a 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 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 (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
}


Output:

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

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> (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)
}


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

Note: If we have not used the in type in the above class, then the 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- 

Kotlin




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>


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

Kotlin




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


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- 

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>) {
    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)
 
}


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




// 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)
}


Output:

GeeksforGeeks


Last Updated : 20 Mar, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads