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à out7.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 Dog7.5 Bảng tóm tắt Variance
| Variance | Keyword | Subtyping | T xuất hiện ở | Ví dụ |
|---|---|---|---|---|
| Invariant | (none) | A<Dog> ≠ A<Animal> | Both | MutableList |
| Covariant | out | A<Dog> ⊆ A<Animal> | Return only | List, Producer |
| Contravariant | in | A<Animal> ⊆ A<Dog> | Param only | Comparator, 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
| Concept | Syntax | Meaning |
|---|---|---|
| Generic class | class Box<T> | T là type parameter |
| Upper bound | <T : Bound> | T phải là subtype của Bound |
| Multiple bounds | where T : A, T : B | T 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 |
| Reified | inline fun <reified T> | Giữ type info at runtime |
PECS Principle:
- Producer =
out(Producer Extends) - Consumer =
in(Consumer Super)
Last updated on