Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A Kotlin generic class is a class parameterized over types, defined by declaring one or more formal type parameters within angle brackets (<>) immediately following the class name. This mechanism enables the creation of type-safe data structures by deferring the specification of exact concrete types until the class is instantiated, allowing the compiler to enforce type constraints statically.

Basic Syntax and Instantiation

Type parameters are conventionally named with single uppercase letters (e.g., T for Type, K for Key, V for Value). These parameters can be used as property types, return types, or function parameter types within the class body.
class Container<T>(var item: T) {
    fun updateItem(newItem: T) {
        item = newItem
    }
}
When instantiating a generic class, the type argument is either explicitly declared or inferred by the Kotlin compiler based on the constructor arguments.
// Explicit type argument
val explicitContainer = Container<Int>(42)

// Inferred type argument
val inferredContainer = Container("Kotlin") 

Declaration-Site Variance

Kotlin utilizes declaration-site variance to define subtyping rules for generic classes using the out and in modifiers. By default, generic classes in Kotlin are invariant, meaning Container<String> is not a subtype of Container<Any>.

Covariance (out)

The out modifier marks a type parameter as covariant. It restricts the type parameter to only be produced (returned from functions or exposed as read-only properties). If Derived is a subtype of Base, then Producer<Derived> is a subtype of Producer<Base>.
class Producer<out T>(private val value: T) {
    fun get(): T = value
    // fun set(value: T) // Compilation error: T cannot be consumed
}

Contravariance (in)

The in modifier marks a type parameter as contravariant. It restricts the type parameter to only be consumed (passed as arguments to functions). If Derived is a subtype of Base, then Consumer<Base> is a subtype of Consumer<Derived>.
class Consumer<in T> {
    fun process(value: T) {
        // ...
    }
    // fun get(): T // Compilation error: T cannot be produced
}

Type Constraints (Bounds)

Type constraints restrict the set of types that can be substituted for a given type parameter.

Single Upper Bound

The most common constraint is an upper bound, denoted by a colon (:). The default upper bound if none is specified is Any?.
// T must be a subtype of Number
class NumericBox<T : Number>(val value: T)

Multiple Upper Bounds

If a type parameter must satisfy multiple constraints, Kotlin requires a where clause at the end of the class declaration.
// T must implement both CharSequence and Comparable
class StringProcessor<T>(val data: T) where T : CharSequence, T : Comparable<T> {
    fun process() {
        // ...
    }
}

Type Erasure and Star-Projections

Kotlin generics are subject to type erasure at runtime. The compiler ensures type safety, but the generic type information is not retained in the compiled bytecode. For example, both Container<Int> and Container<String> are erased to Container. When the specific type argument is unknown or irrelevant, but the class must still be used safely, Kotlin provides star-projections (*).
class Wrapper<T>(var item: T)

// The exact type is unknown, treated as Wrapper<out Any?> for reading 
// and Wrapper<in Nothing> for writing.
fun inspect(wrapper: Wrapper<*>) {
    val item: Any? = wrapper.item // Safe to read as Any?
    // wrapper.item = "New" // Compilation error: Cannot write to star-projected type
}
Master Kotlin with Deep Grasping Methodology!Learn More