Skip to Content

Generics trong Kotlin

1. Giới thiệu

Generics cho phép viết code linh hoạt, type-safe với nhiều kiểu dữ liệu. Thay vì viết nhiều versions của cùng một class/function cho các types khác nhau, generics cho phép parameterize types.

2. Generic Class

class Box<T>(val value: T) { fun get(): T = value } fun main() { val intBox = Box(42) val stringBox = Box("Hello") println(intBox.get()) // 42 println(stringBox.get()) // Hello }

3. Generic Function

fun <T> singletonList(item: T): List<T> { return listOf(item) } fun <T> T.toSingletonList(): List<T> { return listOf(this) } fun main() { val list = singletonList("Hello") val list2 = 42.toSingletonList() }

4. Nhiều Type Parameters

class Pair<K, V>(val first: K, val second: V) fun <K, V> createPair(key: K, value: V) = Pair(key, value) fun main() { val pair = Pair("name", 25) println("${pair.first}: ${pair.second}") }

5. Upper Bounds (Constraints)

fun <T : Comparable<T>> max(a: T, b: T): T { return if (a > b) a else b } fun <T : Number> double(value: T): Double { return value.toDouble() * 2 } fun main() { println(max(5, 3)) // 5 println(max("b", "a")) // b println(double(5)) // 10.0 }

6. Multiple Bounds với where

interface Printable { fun print() } fun <T> process(item: T) where T : Comparable<T>, T : Printable { item.print() } // Ví dụ class thỏa mãn cả 2 bounds class Document(val title: String) : Comparable<Document>, Printable { override fun compareTo(other: Document) = title.compareTo(other.title) override fun print() = println("Document: $title") }

7. Variance: Hiểu về Invariance, Covariance, Contravariance

7.1 Vấn đề: Tại sao cần Variance?

open class Animal class Dog : Animal() class Cat : Animal() // Dog là subtype của Animal val dog: Animal = Dog() // OK // Nhưng List<Dog> có phải subtype của List<Animal>? val dogs: List<Dog> = listOf(Dog()) val animals: List<Animal> = dogs // OK trong Kotlin! (Tại sao?)

7.2 Invariance (Bất biến)

Mặc định, generics trong Kotlin là invariant - Box<Dog> KHÔNG phải subtype của Box<Animal>.

class MutableBox<T>(var value: T) fun main() { val dogBox: MutableBox<Dog> = MutableBox(Dog()) // val animalBox: MutableBox<Animal> = dogBox // ERROR! // Tại sao? Vì nếu cho phép: // animalBox.value = Cat() // Đặt Cat vào box dự kiến chứa Dog! }

7.3 Covariance (Hiệp biến) với out

Khi class chỉ produce (trả về) T, có thể dùng out:

// out T: T chỉ xuất hiện ở vị trí "out" (return types) class Producer<out T>(private val value: T) { fun get(): T = value // OK - T ở vị trí out // fun set(t: T) {} // ERROR - T không thể ở vị trí in } fun main() { val dogProducer: Producer<Dog> = Producer(Dog()) val animalProducer: Producer<Animal> = dogProducer // OK! val animal: Animal = animalProducer.get() // An toàn! }

List trong Kotlin là covariant: interface List<out E>

val dogs: List<Dog> = listOf(Dog()) val animals: List<Animal> = dogs // OK vì List là out

7.4 Contravariance (Nghịch biến) với in

Khi class chỉ consume (nhận vào) T, có thể dùng in:

// in T: T chỉ xuất hiện ở vị trí "in" (parameter types) class Consumer<in T> { fun consume(item: T) { // OK - T ở vị trí in println("Consuming: $item") } // fun get(): T = ... // ERROR - T không thể ở vị trí out } fun main() { val animalConsumer: Consumer<Animal> = Consumer() val dogConsumer: Consumer<Dog> = animalConsumer // OK! dogConsumer.consume(Dog()) // An toàn - consume Dog như Animal }

Comparable là contravariant: interface Comparable<in T>

fun feed(feeder: Consumer<Dog>) { feeder.consume(Dog()) } val animalFeeder: Consumer<Animal> = Consumer() feed(animalFeeder) // OK! Ai feed được Animal thì feed được Dog

7.5 Bảng tóm tắt Variance

VarianceKeywordSubtypingT xuất hiện ởVí dụ
Invariant(none)A<Dog>A<Animal>BothMutableList
CovariantoutA<Dog>A<Animal>Return onlyList, Producer
ContravariantinA<Animal>A<Dog>Param onlyComparator, Consumer

8. Declaration-site vs Use-site Variance

8.1 Declaration-site (Khai báo tại định nghĩa)

// Variance được khai báo khi định nghĩa class/interface interface Source<out T> { fun next(): T } interface Sink<in T> { fun put(item: T) }

8.2 Use-site (Khai báo tại nơi sử dụng)

Khi class invariant nhưng function chỉ cần một chiều:

// Array là invariant class Array<T>(val size: Int) { operator fun get(index: Int): T = ... operator fun set(index: Int, value: T) = ... } // Nhưng function này chỉ đọc từ source -> dùng out tại use-site fun copy(from: Array<out Any>, to: Array<Any>) { for (i in from.indices) { to[i] = from[i] } } // Function này chỉ ghi vào dest -> dùng in tại use-site fun fill(dest: Array<in String>, value: String) { for (i in dest.indices) { dest[i] = value } } fun main() { val strings: Array<String> = arrayOf("a", "b", "c") val anys: Array<Any> = arrayOf(1, 2, 3) copy(strings, anys) // OK! strings là Array<out Any> fill(anys, "x") // OK! anys là Array<in String> }

9. Star Projection (*)

Khi không biết hoặc không quan tâm type argument:

// * = "out Nothing" cho producers, "in Any?" cho consumers fun printList(list: List<*>) { list.forEach { println(it) } // it là Any? } fun main() { printList(listOf(1, 2, 3)) printList(listOf("a", "b", "c")) }

Star projection vs Any

// List<*>: Có thể là List<String>, List<Int>, etc. // List<Any>: List chứa bất kỳ type nào val stringList: List<String> = listOf("a", "b") val starList: List<*> = stringList // OK // val anyList: List<Any> = stringList // ERROR (nếu List là invariant)

10. Reified Type Parameters

Type erasure: generics bị xóa at runtime. reified giữ type info:

// Không có reified - KHÔNG compile // fun <T> isType(value: Any) = value is T // ERROR! // Với reified - OK inline fun <reified T> isType(value: Any): Boolean { return value is T } inline fun <reified T> filterByType(list: List<Any>): List<T> { return list.filterIsInstance<T>() } inline fun <reified T> createInstance(): T? { return T::class.java.getDeclaredConstructor().newInstance() } fun main() { println(isType<String>("Hello")) // true println(isType<Int>("Hello")) // false val mixed = listOf(1, "a", 2, "b", 3.0) println(filterByType<String>(mixed)) // [a, b] println(filterByType<Int>(mixed)) // [1, 2] }

11. Generic Interface

interface Repository<T> { fun save(item: T) fun findById(id: Int): T? fun findAll(): List<T> } data class User(val id: Int, val name: String) class UserRepository : Repository<User> { private val data = mutableMapOf<Int, User>() override fun save(item: User) { data[item.id] = item } override fun findById(id: Int) = data[id] override fun findAll() = data.values.toList() }

12. Ví dụ thực tế: Kết hợp các khái niệm

// Covariant Result type sealed class Result<out T> { data class Success<T>(val data: T) : Result<T>() data class Error(val message: String) : Result<Nothing>() } // Contravariant EventHandler interface EventHandler<in E> { fun handle(event: E) } // Kết hợp variance class EventProcessor<in E, out R>( private val handler: EventHandler<E>, private val transformer: (E) -> R ) { fun process(event: E): R { handler.handle(event) return transformer(event) } } // Use-site variance fun <T> copyIfNotNull( source: List<out T?>, dest: MutableList<in T> ) { for (item in source) { if (item != null) dest.add(item) } }

📝 Tóm tắt

ConceptSyntaxMeaning
Generic classclass Box<T>T là type parameter
Upper bound<T : Bound>T phải là subtype của Bound
Multiple boundswhere T : A, T : BT phải implement cả A và B
Covariance<out T>Producer - chỉ trả về T
Contravariance<in T>Consumer - chỉ nhận T
Star projection<*>Unknown type
Reifiedinline fun <reified T>Giữ type info at runtime

PECS Principle:

  • Producer = out (Producer Extends)
  • Consumer = in (Consumer Super)
Last updated on