DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Kotlin Generics — Type Parameters, Variance & Reified Types

Kotlin Generics — Type Parameters, Variance & Reified Types

Kotlin's generic type system provides powerful abstractions for writing reusable, type-safe code. This guide covers the essential concepts.

Generic Basics

Type Parameters and Constraints:

// Basic generic class
class Box<T>(val value: T)

// Type constraints
class Comparable<T : Comparable<T>> {
    fun compare(other: T): Int = value.compareTo(other)
}

// Multiple constraints
interface Processor<T> where T : Comparable<T>, T : CharSequence
Enter fullscreen mode Exit fullscreen mode

Variance: Covariance and Contravariance

Covariance (out): Permits read-only access

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

val stringProducer: Producer<String> = object : Producer<String> {
    override fun produce() = "Hello"
}

// Safe assignment due to out variance
val anyProducer: Producer<Any> = stringProducer
Enter fullscreen mode Exit fullscreen mode

Contravariance (in): Permits write-only access

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

val anyConsumer: Consumer<Any> = object : Consumer<Any> {
    override fun consume(item: Any) { }
}

// Safe assignment due to in variance
val stringConsumer: Consumer<String> = anyConsumer
Enter fullscreen mode Exit fullscreen mode

Star Projection

Use * when the type doesn't matter:

fun printList(list: List<*>) {
    for (item in list) {
        println(item)
    }
}

val stringList: List<String> = listOf("a", "b")
val intList: List<Int> = listOf(1, 2)
printList(stringList)
printList(intList)
Enter fullscreen mode Exit fullscreen mode

Reified Type Parameters

Access type information at runtime with reified:

inline fun <reified T> parseJson(json: String): T {
    val gson = Gson()
    return gson.fromJson(json, T::class.java)
}

// Runtime type check with inline + reified
inline fun <reified T> isInstance(value: Any): Boolean {
    return value is T
}

// Usage
data class User(val name: String, val age: Int)
val user = parseJson<User>(jsonString)
val isString = isInstance<String>("hello")
Enter fullscreen mode Exit fullscreen mode

Intent with Reified:

inline fun <reified T : Activity> Context.startActivity() {
    startActivity(Intent(this, T::class.java))
}

// Usage
startActivity<MainActivity>()
Enter fullscreen mode Exit fullscreen mode

Result Sealed Class Pattern

Type-safe error handling:

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

fun <T> fetchData(): Result<T> {
    return try {
        Result.Success(data)
    } catch (e: Exception) {
        Result.Error(e)
    }
}

// Pattern matching
when (val result = fetchData<User>()) {
    is Result.Success -> println(result.data)
    is Result.Error -> println(result.exception)
    is Result.Loading -> println("Loading")
}
Enter fullscreen mode Exit fullscreen mode

Type-Safe Builder Pattern

class HtmlBuilder {
    private val elements = mutableListOf<String>()

    fun div(init: HtmlBuilder.() -> Unit) {
        elements.add("<div>")
        this.init()
        elements.add("</div>")
    }

    fun p(content: String) {
        elements.add("<p>$content</p>")
    }

    fun build() = elements.joinToString("\n")
}

fun html(init: HtmlBuilder.() -> Unit): String {
    val builder = HtmlBuilder()
    builder.init()
    return builder.build()
}

// Usage
val page = html {
    div {
        p("Welcome")
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Use type parameters for generic reusability
  • Apply constraints to limit permissible types
  • Leverage variance (out/in) for flexible APIs
  • Use reified in inline functions for runtime type access
  • Employ sealed classes for type-safe error handling
  • Build type-safe DSLs with builder patterns

8 Android app templates: Gumroad

Top comments (0)