Kotlin Generics

Generics let you write code that works with any type, while still keeping type safety. Instead of writing separate functions or classes for Int, String, Double, and so on, you write one generic version that works for all of them.

The Problem Generics Solve


Without generics — repetitive:
  fun printInt(value: Int) = println(value)
  fun printString(value: String) = println(value)
  fun printDouble(value: Double) = println(value)
  // One function per type — not scalable

With generics — one function, any type:
  fun  print(value: T) = println(value)
  print(42)       // works
  print("Hello")  // works
  print(3.14)     // works

Generic Functions

fun  firstOrNull(list: List): T? = if (list.isEmpty()) null else list[0]

fun  swap(pair: Pair): Pair = Pair(pair.second, pair.first)

fun main() {
    println(firstOrNull(listOf(10, 20, 30)))     // 10
    println(firstOrNull(listOf()))           // null
    println(firstOrNull(listOf("a", "b")))        // a

    println(swap(Pair(1, 2)))                     // (2, 1)
    println(swap(Pair("Hi", "Bye")))              // (Bye, Hi)
}

Generic Classes

class Box(val content: T) {
    fun describe() = "Box containing: $content (${content!!::class.simpleName})"
    fun unwrap(): T = content
}

fun main() {
    val intBox    = Box(42)
    val stringBox = Box("Kotlin")
    val listBox   = Box(listOf(1, 2, 3))

    println(intBox.describe())     // Box containing: 42 (Int)
    println(stringBox.describe())  // Box containing: Kotlin (String)
    println(listBox.unwrap())      // [1, 2, 3]
}

Multiple Type Parameters

class KeyValue(val key: K, val value: V) {
    override fun toString() = "$key → $value"
}

fun  mapOf2(k1: K, v1: V, k2: K, v2: V) = listOf(
    KeyValue(k1, v1),
    KeyValue(k2, v2)
)

fun main() {
    val entry = KeyValue("userId", 12345)
    println(entry)   // userId → 12345

    val table = mapOf2("a", 1, "b", 2)
    table.forEach { println(it) }
}

Type Constraints

Use : UpperBound to restrict which types can be used with a generic:

// T must be a Number (Int, Double, Long, etc.)
fun  sum(a: T, b: T): Double = a.toDouble() + b.toDouble()

// T must implement Comparable
fun > largest(a: T, b: T): T = if (a > b) a else b

fun main() {
    println(sum(10, 20))         // 30.0
    println(sum(3.5, 1.5))      // 5.0
    println(largest(100, 200))  // 200
    println(largest("apple", "mango")) // mango (alphabetically greater)
}

Generic Interface

interface Repository {
    fun save(item: T)
    fun findById(id: Int): T?
    fun findAll(): List
}

data class Product(val id: Int, val name: String)

class ProductRepository : Repository {
    private val store = mutableMapOf()

    override fun save(item: Product) { store[item.id] = item }
    override fun findById(id: Int): Product? = store[id]
    override fun findAll(): List = store.values.toList()
}

fun main() {
    val repo = ProductRepository()
    repo.save(Product(1, "Laptop"))
    repo.save(Product(2, "Mouse"))

    println(repo.findById(1))   // Product(id=1, name=Laptop)
    println(repo.findAll())
}

Practical Example: Generic Stack

class Stack {
    private val items = mutableListOf()

    fun push(item: T) { items.add(item) }
    fun pop(): T? = if (items.isEmpty()) null else items.removeAt(items.lastIndex)
    fun peek(): T? = items.lastOrNull()
    val size: Int get() = items.size
    val isEmpty: Boolean get() = items.isEmpty()
    override fun toString() = items.toString()
}

fun main() {
    val stack = Stack()
    stack.push("First")
    stack.push("Second")
    stack.push("Third")
    println(stack)         // [First, Second, Third]
    println(stack.pop())   // Third
    println(stack.peek())  // Second
    println(stack.size)    // 2
}

Leave a Comment

Your email address will not be published. Required fields are marked *