Logo

dev-resources.site

for different kinds of informations.

Kotlin Generics Simplified

Published at
1/14/2025
Categories
kotlin
android
androiddev
Author
khushpanchal123
Categories
3 categories in total
kotlin
open
android
open
androiddev
open
Author
15 person written this
khushpanchal123
open
Kotlin Generics Simplified

Generics in Kotlin allow us to write flexible and reusable code. In this article, weโ€™ll dive into the world of Kotlin Generics and cover the following topics:

  • What are Generics and Why Do We Need Them?
  • Generic Functions
  • Constraints in Generics
  • Type Erasure in Generics
  • Variance in Generics

What are Generics and Why Do We Need Them?

Generics enable us to write a single class, function, or property that can handle different types. This reduces code duplication and increases flexibility.

Imagine weโ€™re building an app where users answer questions. We start with a simple Question class:

data class Question(
    val questionText: String,
    val answer: String
)

fun main() {
    val question = Question("Name your favorite programming language", "Kotlin")
}
Enter fullscreen mode Exit fullscreen mode

This works fine for text answers. But what if the answer can also be a number or a boolean? Weโ€™d need multiple versions of the Question class or sacrifice type safety by using Any. Both approaches are less than ideal.

Solution: Generics

Generics allow us to make the Question class type-flexible by introducing a type parameter <T>:

data class Question<T>(
    val questionText: String,
    val answer: T
)

fun main() {
    val questionString = Question("What's your favorite programming language?", "Kotlin")
    val questionBoolean = Question("Kotlin is statically typed?", true)
    val questionInt = Question("How many days are in a week?", 7)
}
Enter fullscreen mode Exit fullscreen mode

Here, <T> is a placeholder for the type of answer. The compiler ensures type safety while providing flexibility. If needed, you can explicitly specify the type:

val questionInt = Question<Int>("2 + 2 equals?", 4)
Enter fullscreen mode Exit fullscreen mode

Why Use Generics?

  • Avoid code duplication.
  • Increase reusability.
  • Maintain type safety.

Generic Functions

Generics arenโ€™t just for classes โ€” they work with functions too! To define a generic function, place the type parameter <T> before the function name.

fun <T> printValue(value: T) {
    println(value)
}

fun <T> T.toCustomString(): String { // Generic extension function
    return "Value: $this"
}

fun main() {
    printValue("Hello, Kotlin!")
    printValue(42)
    println(3.14.toCustomString())
}
Enter fullscreen mode Exit fullscreen mode

Generics Constraints

Sometimes, we need the type parameter to meet certain requirements. Constraints restrict the types that can be used as arguments for generics.

interface Movable {
    fun move()
}

class Car(private val make: String, private val model: String) : Movable {
    override fun move() {
        println("$make $model is moving.")
    }
}

fun <T : Movable> run(vehicle: T) {
    vehicle.move()
}

fun main() {
    run(Car("BMW", "X3 M"))
}
Enter fullscreen mode Exit fullscreen mode

Here, the <T : Movable> constraint ensures that only types implementing Movable can be used. The type specified after a colon is the upper bound (Movable).

The default upper bound (if there was none specified) is Any?

Only one upper bound can be specified inside the angle brackets. If the same type parameter needs more than one upper bound, we need a separate where-clause:

interface Flyable {
    fun fly()
}

class Plane(private val make: String, private val model: String) : Movable, Flyable {
    override fun move() {
        println("$make $model is moving.")
    }

    override fun fly() {
        println("$make $model is flying.")
    }
}

fun <T> operate(vehicle: T) where T : Movable, T : Flyable {
    vehicle.move()
    vehicle.fly()
}

fun main() {
    operate(Plane("Boeing", "747"))
}
Enter fullscreen mode Exit fullscreen mode

The where clause ensures that the type T satisfies multiple constraints.

Type Erasure in Generics

At compile time, the compiler removes the type argument from the function call. This is called type erasure. The reified keyword retains the type information at runtime, but it can only be used in inline functions. Reified let us use reflection on type parameter. Function must be inline to use reified.

// Generics reified
fun <T> printSomething(value: T) {
    println(value.toString())// OK
//    println("Doing something with type: ${T::class.simpleName}") // Error
}

inline fun <reified T> doSomething(value: T) {
    println("Doing something with type: ${T::class.simpleName}") // OK
}
Enter fullscreen mode Exit fullscreen mode

Variance in Generics

Variance modifiers out and in help make generics flexible by controlling how subtypes are treated. Kotlin offers three variance:

  • Invariant (T)
  • Covariant (out T)
  • Contravariant (in T)

Invariant Generics

By default, Kotlin generics are invariant. This means that even if B is a subclass of A, Container<B> is not a subclass of Container<A>.

class Container<T>(val item: T)

fun main() {
    val intContainer = Container(10)
    // val numberContainer: Container<Number> = intContainer // Error: Type mismatch
}
Enter fullscreen mode Exit fullscreen mode

Invariant generics ensure that no unsafe operations occur, as Container<T> guarantees the exact type of T.

Covariant Generics (out T)

The out modifier is used when a generic class or function only produces values of type T. It ensures that subtypes are preserved, meaning if Dog is a subtype of Animal, then Producer<Dog> is also a subtype of Producer<Animal>.

open class Animal
class Dog : Animal()

class AnimalProducer<out T>(private val instance: T) {
    fun produce(): T = instance
    // fun consume(value: T) { /* ... */ } // This will show compile time error
}

fun main() {
    val dogProducer: AnimalProducer<Animal> = AnimalProducer(Dog())
    println("Produced: ${dogProducer.produce()}") // Works because of `out`
}
Enter fullscreen mode Exit fullscreen mode

Contravariant Generics (in T)

The in modifier is used when a generic class or function only consumes values of type T. It reverses the subtyping relationship. If Dog is a subtype of Animal, then Consumer<Animal> is a subtype of Consumer<Dog>.

class AnimalConsumer<in T> {
    fun consume(value: T) {
        println("Consumed: ${value.toString()}")
    }
    // fun produce(): T { /* ... */ } //This will show compile time error
}

fun main() {
    val animalConsumer: AnimalConsumer<Dog> = AnimalConsumer<Animal>()
    animalConsumer.consume(Dog()) // Works because of `in`
}
Enter fullscreen mode Exit fullscreen mode

Source Code: GitHub
Contact Me: LinkedIn | Twitter

Happy coding! โœŒ๏ธ

android Article's
30 articles in total
Favicon
Three Common Pitfalls in Modern Android Development
Favicon
Using SVGs on Canvas with Compose Multiplatform
Favicon
Kotlin Generics Simplified
Favicon
Understanding Process Management in Operating Systems
Favicon
Introduction to Operating Systems
Favicon
Why Should You Develop a Native Android App Over Flutter?
Favicon
Flutter Development for Low end PCs
Favicon
Day 13 of My Android Adventure: Crafting a Custom WishList App with Sir Denis Panjuta
Favicon
How to Integrate Stack, Bottom Tab, and Drawer Navigator in React Native
Favicon
Flutter Design Pattern Bussines Logic Component (BLOC)
Favicon
๐ŸŒŽ Seamless Multi-Language Support in React Native
Favicon
Morphing Geometric Shapes with SDF in GLSL Fragment Shaders and Visualization in Jetpack Compose
Favicon
FMWhatsApp - Enhanced WhatsApp Experience
Favicon
[Feedback Wanted]Meet Nora: A Desktop Plant Robot Companion ๐ŸŒฑ
Favicon
GBWhatsApp - Advanced WhatsApp Alternative
Favicon
Android TreeView(Hierarchy based) with Kotlin
Favicon
The Role of Android in IoT Development
Favicon
PicsArt MOD APK: Unlock the Power of Creativity
Favicon
Getting Started with Android Testing: Building Reliable Apps with Confidence (Part 3/3)
Favicon
How to Integrate Stack and Bottom Tab Navigator in React Native
Favicon
Day 11 Unlocking the Magic of Location Services!
Favicon
Creating M3U Playlists for xPola Player
Favicon
xPola Player: The Advanced Media Player for Android
Favicon
Getting Started with Android Testing: Building Reliable Apps with Confidence (Part 2/3)
Favicon
How I wrote this technical post with Nebo: an Android gamechanger โœ๏ธ
Favicon
Understanding Room Database in Android: A Beginner's Guide
Favicon
hiya
Favicon
Android Lock Screen Widgets: Revolutionizing Content Consumption with Glance's Smart Features
Favicon
Top 10 Best Android App Development Companies in India
Favicon
Why Modded APKs Are Gaining Popularity in 2025: Understanding the Risks and Benefits

Featured ones: