Sobes.tech
Middle

How to work with generic types (generics) in Kotlin?

sobes.tech AI

Answer from AI

Generic types in Kotlin allow creating classes, interfaces, and functions that work with data of different types, while maintaining type safety at compile time.

Main concepts and usage:

  • Declaring generic classes and interfaces:
    class Box<T>(val item: T) // Generic class with type parameter T
    interface MyList<E> { // Generic interface with type parameter E
        fun add(element: E)
        fun get(index: Int): E
    }
    
  • Declaring generic functions:
    fun <T> printAnything(value: T) { // Generic function with type parameter T
        println(value)
    }
    
  • Creating instances of generic types:
    val intBox: Box<Int> = Box(123) // Specifying a concrete type (Int)
    val stringBox = Box("hello") // Compiler infers the type (String)
    
  • Variance: Defines how subtyping between more complex types relates to subtyping between their component types.
    • Covariance (out): Allows using a type and its subtypes. Denoted as out. Example: List<out Any> can contain List<String>. Used for types that produce values (e.g., reading from a collection).
      interface Producer<out T> { // out T - covariant type parameter
           fun produce(): T
      }
      
    • Contravariance (in): Allows using a type and its supertypes. Denoted as in. Example: Comparator<in String> can work with Comparator<Any>. Used for types that consume values (e.g., writing to a collection).
      interface Consumer<in T> { // in T - contravariant type parameter
          fun consume(value: T)
      }
      
    • Invariance: The type in a generic parameter must be exactly the same. This is the default behavior.
  • Type constraints (where): Allows restricting the type parameter to implement certain interfaces or inherit from a specific class.
    fun <T : Comparable<T>> max(a: T, b: T): T { // T must implement Comparable
        return if (a > b) a else b
    }
    
    fun <T> process(item: T) where T : Runnable, T : Comparable<T> {
        item.run()
        // ... using comparability
    }
    
  • Star projection (*): Used when specific type arguments are not important. Similar to <?> in Java.
    fun printList(list: List<*>) { // Can accept a list of any type
         for (item in list) {
             println(item)
         }
    }
    

Generic types in Kotlin use type erasure, like in Java. Information about specific type parameters is lost at runtime. To work with types at runtime, reified types are used.

  • Reified types (reified): Available only for inline functions. Allow preserving type information at runtime.
    inline fun <reified T> checkType(value: Any) { // T is a reified type
        if (value is T) {
            println("Value is of type T")
        } else {
            println("Value is not of type T")
        }
    }
    
How to work with generic types (generics) in Kotlin… - sobes.tech