Bỏ qua để đến nội dung

🧬 Generics

🎯 Mục tiêu: Hiểu Generics - kỹ thuật viết code type-safe và reusable với type parameters, bao gồm variance (in/out) và type constraints.


Hãy tưởng tượng bạn cần viết một class Box để chứa đồ vật. Nếu không có Generics:

// ❌ Cách 1: Tạo nhiều class
class IntBox(val value: Int)
class StringBox(val value: String)
class UserBox(val value: User)
// Phải tạo class mới cho mỗi type!
// ❌ Cách 2: Dùng Any (mất type safety)
class AnyBox(val value: Any)
fun main() {
val box = AnyBox("Hello")
val str: String = box.value // ❌ Lỗi! Phải cast
val str2 = box.value as String // 😱 Có thể crash nếu không phải String
}

Generics giải quyết cả hai vấn đề:

// ✅ Một class, mọi types, type-safe!
class Box<T>(val value: T)
fun main() {
val intBox = Box(42) // Box<Int>
val strBox = Box("Hello") // Box<String>
val userBox = Box(User("A")) // Box<User>
val number: Int = intBox.value // ✅ Type-safe, không cần cast!
val text: String = strBox.value // ✅ Compiler biết đây là String
}

// Generic class với một type parameter
class Box<T>(val value: T) {
fun getValue(): T = value
fun isNull(): Boolean = value == null
}
// Sử dụng
val intBox = Box(42)
val strBox = Box<String>("Hello") // Explicit type
val nullBox = Box<String?>(null) // Nullable type
class Pair<A, B>(val first: A, val second: B) {
override fun toString() = "($first, $second)"
}
class Triple<A, B, C>(val first: A, val second: B, val third: C)
fun main() {
val pair = Pair("Alice", 25) // Pair<String, Int>
val userScore = Pair(User("Bob"), 100.0) // Pair<User, Double>
println(pair.first) // Alice
println(pair.second) // 25
}
class Stack<T> {
private val items = mutableListOf<T>()
fun push(item: T) {
items.add(item)
}
fun pop(): T? = items.removeLastOrNull()
fun peek(): T? = items.lastOrNull()
val isEmpty: Boolean
get() = items.isEmpty()
val size: Int
get() = items.size
override fun toString() = items.toString()
}
fun main() {
val stringStack = Stack<String>()
stringStack.push("First")
stringStack.push("Second")
stringStack.push("Third")
println(stringStack.pop()) // Third
println(stringStack.peek()) // Second
val numberStack = Stack<Int>()
numberStack.push(1)
numberStack.push(2)
println(numberStack) // [1, 2]
}

Hàm cũng có thể có type parameters:

// Generic function
fun <T> singletonList(item: T): List<T> = listOf(item)
// Generic extension function
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null
// Multiple type parameters
fun <T, R> List<T>.mapCustom(transform: (T) -> R): List<R> {
val result = mutableListOf<R>()
for (item in this) {
result.add(transform(item))
}
return result
}
fun main() {
val list = singletonList(42) // List<Int>
val strList = singletonList("Hello") // List<String>
val numbers = listOf(1, 2, 3)
println(numbers.secondOrNull()) // 2
val doubled = numbers.mapCustom { it * 2 }
println(doubled) // [2, 4, 6]
}
fun <T> swap(array: Array<T>, i: Int, j: Int) {
val temp = array[i]
array[i] = array[j]
array[j] = temp
}
fun main() {
val numbers = arrayOf(1, 2, 3, 4, 5)
swap(numbers, 0, 4)
println(numbers.toList()) // [5, 2, 3, 4, 1]
val names = arrayOf("Alice", "Bob", "Charlie")
swap(names, 0, 2)
println(names.toList()) // [Charlie, Bob, Alice]
}

Giới hạn type parameter phải là subtype của một type cụ thể:

// T phải là subtype của Comparable<T>
fun <T : Comparable<T>> max(a: T, b: T): T {
return if (a > b) a else b
}
fun main() {
println(max(5, 10)) // 10
println(max("apple", "banana")) // banana
println(max(3.14, 2.71)) // 3.14
// ❌ Lỗi compile: User không implement Comparable
// println(max(User("A"), User("B")))
}

Khi cần nhiều constraints:

interface Drawable {
fun draw()
}
interface Clickable {
fun onClick()
}
// T phải implement cả Drawable và Clickable
fun <T> renderAndHandle(widget: T) where T : Drawable, T : Clickable {
widget.draw()
widget.onClick()
}
// Ví dụ thực tế
class Button : Drawable, Clickable {
override fun draw() = println("Drawing button")
override fun onClick() = println("Button clicked!")
}
fun main() {
renderAndHandle(Button())
// Drawing button
// Button clicked!
}
// Mặc định T có thể null (upper bound là Any?)
fun <T> process(value: T) {
println(value) // value có thể null
}
// T không thể null (upper bound là Any)
fun <T : Any> processNonNull(value: T) {
println(value.toString()) // An toàn, value không null
}
fun main() {
process(null) // ✅ OK
// processNonNull(null) // ❌ Lỗi compile
}

open class Animal
class Dog : Animal()
class Cat : Animal()
fun main() {
val dogs: List<Dog> = listOf(Dog(), Dog())
// ✅ OK vì List là covariant (out)
val animals: List<Animal> = dogs
// Nhưng với MutableList:
val mutableDogs: MutableList<Dog> = mutableListOf(Dog())
// ❌ Lỗi compile! MutableList không phải covariant
// val mutableAnimals: MutableList<Animal> = mutableDogs
}

out nghĩa là type chỉ được xuất ra (produce), không được nhận vào (consume):

// Producer chỉ OUTPUT type T
interface Producer<out T> {
fun produce(): T
// fun consume(item: T) // ❌ Không được! out = không nhận input
}
class DogProducer : Producer<Dog> {
override fun produce() = Dog()
}
fun main() {
val dogProducer: Producer<Dog> = DogProducer()
// ✅ OK vì out - Dog là subtype của Animal
val animalProducer: Producer<Animal> = dogProducer
val animal: Animal = animalProducer.produce() // Nhận được Dog
}

in nghĩa là type chỉ được nhận vào (consume), không được xuất ra (produce):

// Consumer chỉ INPUT type T
interface Consumer<in T> {
fun consume(item: T)
// fun produce(): T // ❌ Không được! in = không output
}
class AnimalHandler : Consumer<Animal> {
override fun consume(item: Animal) {
println("Handling animal")
}
}
fun main() {
val animalHandler: Consumer<Animal> = AnimalHandler()
// ✅ OK vì in - Animal handler có thể handle Dog
val dogHandler: Consumer<Dog> = animalHandler
dogHandler.consume(Dog())
}
Keyword Tên Hướng Ví dụ real-world
out T Covariant Chỉ output (produce) List<out T>, Flow<out T>
in T Contravariant Chỉ input (consume) Comparable<in T>
(không có) Invariant Cả hai MutableList<T>

Khi không quan tâm đến type cụ thể:

fun printListSize(list: List<*>) {
println("List size: ${list.size}")
// list[0] có type Any? vì chúng ta không biết type cụ thể
}
fun main() {
printListSize(listOf(1, 2, 3)) // List size: 3
printListSize(listOf("a", "b")) // List size: 2
}

Bài tập 1: Generic Result class

// TODO: Tạo sealed class Result<T> với:
// - Success(data: T)
// - Error(message: String)
// - Loading

Lời giải:

sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
data object Loading : Result<Nothing>()
fun getOrNull(): T? = when (this) {
is Success -> data
else -> null
}
fun getOrElse(default: @UnsafeVariance T): T = when (this) {
is Success -> data
else -> default
}
}
fun main() {
val success: Result<Int> = Result.Success(42)
val error: Result<Int> = Result.Error("Something went wrong")
println(success.getOrNull()) // 42
println(error.getOrNull()) // null
println(error.getOrElse(0)) // 0
}

Bài tập 2: Generic Cache class

// TODO: Tạo class Cache<K, V> với:
// - put(key: K, value: V)
// - get(key: K): V?
// - contains(key: K): Boolean
// - remove(key: K): V?
// - clear()

Lời giải:

class Cache<K, V> {
private val storage = mutableMapOf<K, V>()
fun put(key: K, value: V) {
storage[key] = value
}
fun get(key: K): V? = storage[key]
fun contains(key: K): Boolean = storage.containsKey(key)
fun remove(key: K): V? = storage.remove(key)
fun clear() = storage.clear()
val size: Int get() = storage.size
}
fun main() {
val userCache = Cache<Int, String>()
userCache.put(1, "Alice")
userCache.put(2, "Bob")
println(userCache.get(1)) // Alice
println(userCache.contains(3)) // false
userCache.remove(1)
println(userCache.size) // 1
}

// Generic Response wrapper
sealed class ApiResponse<out T> {
data class Success<T>(val data: T) : ApiResponse<T>()
data class Error(val code: Int, val message: String) : ApiResponse<Nothing>()
data object Loading : ApiResponse<Nothing>()
}
// Repository với generic function
class UserRepository {
suspend fun <T> safeApiCall(apiCall: suspend () -> T): ApiResponse<T> {
return try {
ApiResponse.Success(apiCall())
} catch (e: Exception) {
ApiResponse.Error(500, e.message ?: "Unknown error")
}
}
}
// ViewModel với generic LiveData
class BaseViewModel<T> : ViewModel() {
protected val _data = MutableLiveData<ApiResponse<T>>()
val data: LiveData<ApiResponse<T>> = _data
}
// Adapter với generic ViewHolder
abstract class BaseAdapter<T, VH : RecyclerView.ViewHolder> :
RecyclerView.Adapter<VH>() {
protected val items = mutableListOf<T>()
fun submitList(newItems: List<T>) {
items.clear()
items.addAll(newItems)
notifyDataSetChanged()
}
}


Sau bài học này, bạn có thể:

  • Hiểu vấn đề mà Generics giải quyết (type safety + reusability)
  • Tạo generic class với một hoặc nhiều type parameters
  • Tạo generic function và extension function
  • Sử dụng type constraints với : Typewhere
  • Hiểu variance: out (covariant) và in (contravariant)
  • Biết khi nào dùng star projection *
  • Hiểu type erasure và giải pháp với reified

Tiếp theo: Type Aliases