ProgrammingBackend Developer

How are generics implemented in Kotlin? What limitations exist, how does invariance, covariance, and contravariance work, and how do they differ from generics in Java? Provide examples of usage.

Pass interviews with Hintsage AI assistant

Answer

Generics in Kotlin allow for the creation of universal and type-safe data structures and functions. The main feature: Kotlin implements the generics type system at compile-time, like Java, but with stricter type safety and extended syntax for variance (covariance and contravariance).

Limitations of generics:

  • Type parameters are invariant by default.
  • You cannot create an instance of a type parameter (T() is prohibited).
  • There is no access to type information of generics at runtime (type erasure).

Variance:

  • Covariance (out T): allows the use of subtypes.
  • Contravariance (in T): allows the use of supertypes.
  • Invariance: a type without a variance modifier.

Example of covariance:

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

Example of contravariance:

interface Consumer<in T> { fun consume(item: T) }

Difference from Java:

  • The syntax is more explicit and concise (out instead of ? extends, in instead of ? super).
  • There are no wildcard types (?, only in/out).
  • Creation of an instance of a generic parameter is prohibited.

Trick Question

Question: "Can you declare an array of arrays in Kotlin (Array<Array<Int>>) as Array<out Array<Int>> and what will happen if you try to write to such an array?"

Answer: Yes, you can declare it as Array<out Array<Int>>, but such an array becomes read-only:

val arr: Array<out Array<Int>> = Array(1) { Array(1) { 0 } } arr[0] = arrayOf(1, 2, 3) // Compilation error!

Attempting to write a value will result in an error — a generic array with an out parameter does not allow writing elements, as this would violate type safety.

Examples of real errors due to lack of knowledge of the topic


Story

The team tried to create an array of generic objects with an out parameter type, and then attempted to put values into it via set(index, value). The code compiled, but caused runtime errors, and several functions became non-working.


Story

Once during the migration of a library from Java to Kotlin, wildcards (? extends ...) were left, and in Kotlin, the types were simply copied without changing to out/in. The result — compilation failed, and during "traversing" the error occurred at runtime, complicating the debugging process.


Story

Used in/out variance with a custom class, but mixed up the modifiers, declaring interface Stack<in T> instead of Stack<out T>. This led to the impossibility of returning elements from the stack: the method signature violated the system out/in contract.