Advanced Kotlin Generics Tutorial: Generic Classes, Generic Functions, Type Constraints, and Variance


This Advanced Kotlin Generics tutorial explains how to write reusable and type-safe code using generics in Kotlin. It covers generic classes, generic functions, type constraints, and variance (in and out) with clear examples and best practices. This chapter helps developers avoid code duplication and build flexible, strongly typed Kotlin applications.

Advanced Kotlin Concepts – Generics (Complete Tutorial)

What Are Generics?

Generics allow classes and functions to work with different data types while maintaining type safety.

Without generics, you would need separate implementations for each data type.

Generic Classes

A generic class can work with any type specified during object creation.

Syntax


class Box<T>(val value: T)

Example


fun main() {
val intBox = Box(10)
val stringBox = Box("Kotlin")
println(intBox.value)
println(stringBox.value)
}

Best Practices

  1. Use meaningful generic type names (T, E, K, V).
  2. Avoid overusing generics where simple types suffice.

Generic Functions

Generic functions allow functions to operate on different types.

Syntax


fun <T> printItem(item: T) {
println(item)
}

Example


fun main() {
printItem(100)
printItem("Hello Kotlin")
printItem(3.14)
}

Best Practices

  1. Use generic functions for utility or reusable logic.
  2. Keep generic functions simple and focused.

Type Constraints

Type constraints restrict the types that can be used with generics.

Example


fun <T : Number> sum(a: T, b: T): Double {
return a.toDouble() + b.toDouble()
}

fun main() {
println(sum(10, 20))
println(sum(3.5, 4.5))
}

Multiple Constraints


fun <T> printInfo(item: T) where T : CharSequence, T : Comparable<T> {
println(item)
}

Best Practices

  1. Use type constraints to ensure valid operations.
  2. Prefer constraints over unsafe casting.

Variance (in and out)

Variance defines how generic types relate to each other.

out (Covariance)

  1. Used when a generic type is only produced (read-only).
  2. Allows assignment of subtypes.

class Producer<out T>(private val value: T) {
fun produce(): T = value
}

fun main() {
val producer: Producer<Number> = Producer(10)
println(producer.produce())
}

in (Contravariance)

  1. Used when a generic type is only consumed (write-only).
  2. Allows assignment of supertypes.

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

fun main() {
val consumer: Consumer<Int> = Consumer<Number>()
consumer.consume(20)
}

Invariance (Default)


class Box<T>(val value: T)

By default, Kotlin generics are invariant.

Best Practices for Variance

  1. Use out for producers.
  2. Use in for consumers.
  3. Remember: Producer → out, Consumer → in.

Summary

This chapter covered advanced Kotlin generics, including generic classes, generic functions, type constraints, and variance (in and out). Proper use of generics improves code reuse, type safety, and flexibility, making Kotlin applications more robust and maintainable.