Blog Infos
Author
Published
Topics
, , , ,
Published

Generic types in Kotlin provide a powerful way to create classes, interfaces, and functions that can operate on any type, while maintaining type safety. They allow you to write flexible and reusable code. Here’s a comprehensive guide to generic types in Kotlin:

1. Basics of Generics

Generics allow you to define a placeholder for a type, which can be specified when the generic class or function is instantiated or called.

Generic Classes

You define a generic class by specifying a type parameter in angle brackets <T>:

class Box<T>(val value: T)

val intBox = Box(1) // Box<Int>
val stringBox = Box("Hello") // Box<String>val intBox = Box(1) // Box<Int>
val stringBox = Box("Hello") // Box<String>
Generic Functions

Functions can also be generic:

fun <T> box(value: T): Box<T> {
    return Box(value)
}

val boxedInt = box(1)
val boxedString = box("Hello")
2. Type Constraints

Type constraints restrict the types that can be used as type arguments.

Upper Bounds

You can specify an upper bound using the syntax:

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

val result = sum(1, 2) // OK
val error = sum(1, "two") // Error: Type mismatch
Multiple Constraints

Multiple constraints can be specified using the where clause:

fun <T> ensureNotNull(value: T) where T : Any, T : Comparable<T> {
    // Implementation
}
3. Variance

Variance annotations control how subtypes of generic types are related to each other.

Covariance (out)

Covariant types can be used as the return type, but not as the parameter type. This means that Producer<out T> can produce T but cannot consume T.

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

val anyProducer: Producer<Any> = Producer<String>("Hello")
Contravariance (in)

Contravariant types can be used as the parameter type, but not as the return type. This means that Consumer<in T> can consume T but cannot produce T.

class Consumer<in T> {
    fun consume(value: T) {
        // Implementation
    }
}

val stringConsumer: Consumer<String> = Consumer<Any>()
Invariance

Without variance annotations, generic types are invariant, meaning Container<String> is not a subtype of Container<Any>, even if String is a subtype of Any.

class Container<T>(private val value: T) {
    fun getValue(): T = value
    fun setValue(newValue: T) {
        value = newValue
    }
}

// This would be an error:
// val anyContainer: Container<Any> = Container<String>("Hello")
4. Generic Constraints in Functions

Kotlin allows constraints directly in the function declaration:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
        where T : CharSequence, T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

Job Offers

Job Offers


    Senior Android Developer

    SumUp
    Berlin
    • Full Time
    apply now

    Senior Android Engineer

    Carly Solutions GmbH
    Munich
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

Jobs

5. Reified Type Parameters

Type parameters in inline functions can be marked as reified, allowing you to access the type at runtime:

inline fun <reified T> isOfType(value: Any): Boolean {
    return value is T
}

val result = isOfType<String>("Hello") // true
6. Star Projections

Star projections are a way to work with generic types when you don’t know the specific type parameter:

fun printList(list: List<*>) {
    list.forEach { println(it) }
}

printList(listOf("Hello", "World"))
printList(listOf(1, 2, 3))
7. Generic Type Aliases

Type aliases can simplify complex generic types:

typealias StringMap<T> = Map<String, T>
val map: StringMap<Int> = mapOf("one" to 1, "two" to 2)
8. Advanced Example: Generic Repository

Here’s an advanced example of a generic repository:

interface Repository<T> {
    fun getById(id: Int): T
    fun save(item: T)
}
class UserRepository : Repository<User> {
    override fun getById(id: Int): User {
        // Implementation
    }
    override fun save(item: User) {
        // Implementation
    }
}
Conclusion

Generics in Kotlin are a powerful feature that enhances code reusability and type safety. By understanding and utilizing generic types, constraints, variance, and other related concepts, you can write more flexible and robust Kotlin code.

4o

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
Let’s suppose that for some reason we are interested in doing some tests with…
READ MORE

Leave a Reply

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

Fill out this field
Fill out this field
Please enter a valid email address.

Menu