🧬 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.
💡 Vấn đề mà Generics giải quyết
Phần tiêu đề “💡 Vấn đề mà Generics giải quyết”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 classclass 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
Phần tiêu đề “📝 Generic Class”Class cơ bản
Phần tiêu đề “Class cơ bản”// Generic class với một type parameterclass Box<T>(val value: T) { fun getValue(): T = value fun isNull(): Boolean = value == null}
// Sử dụngval intBox = Box(42)val strBox = Box<String>("Hello") // Explicit typeval nullBox = Box<String?>(null) // Nullable typeClass với nhiều type parameters
Phần tiêu đề “Class với nhiều type parameters”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}Ví dụ thực tế: Generic Stack
Phần tiêu đề “Ví dụ thực tế: Generic Stack”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]}🔧 Generic Function
Phần tiêu đề “🔧 Generic Function”Hàm cũng có thể có type parameters:
// Generic functionfun <T> singletonList(item: T): List<T> = listOf(item)
// Generic extension functionfun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null
// Multiple type parametersfun <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]}Ví dụ thực tế: Swap function
Phần tiêu đề “Ví dụ thực tế: Swap function”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]}🎯 Type Constraints
Phần tiêu đề “🎯 Type Constraints”Upper Bound (Giới hạn trên)
Phần tiêu đề “Upper Bound (Giới hạn trên)”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")))}Multiple Constraints với where
Phần tiêu đề “Multiple Constraints với where”Khi cần nhiều constraints:
interface Drawable { fun draw()}
interface Clickable { fun onClick()}
// T phải implement cả Drawable và Clickablefun <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!}Nullable constraints
Phần tiêu đề “Nullable constraints”// 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}📊 Variance: in và out
Phần tiêu đề “📊 Variance: in và out”Vấn đề covariance
Phần tiêu đề “Vấn đề covariance”open class Animalclass 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 = Producer (Covariant)
Phần tiêu đề “out = Producer (Covariant)”out nghĩa là type chỉ được xuất ra (produce), không được nhận vào (consume):
// Producer chỉ OUTPUT type Tinterface 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 = Consumer (Contravariant)
Phần tiêu đề “in = Consumer (Contravariant)”in nghĩa là type chỉ được nhận vào (consume), không được xuất ra (produce):
// Consumer chỉ INPUT type Tinterface 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())}Tóm tắt Variance
Phần tiêu đề “Tóm tắt Variance”| 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> |
⭐ Star Projection
Phần tiêu đề “⭐ Star Projection”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}🛠️ Thực hành
Phần tiêu đề “🛠️ Thực hành”Bài tập 1: Generic Result class
// TODO: Tạo sealed class Result<T> với:// - Success(data: T)// - Error(message: String)// - LoadingLờ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}📱 Trong Android
Phần tiêu đề “📱 Trong Android”// Generic Response wrappersealed 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 functionclass 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 LiveDataclass BaseViewModel<T> : ViewModel() { protected val _data = MutableLiveData<ApiResponse<T>>() val data: LiveData<ApiResponse<T>> = _data}
// Adapter với generic ViewHolderabstract 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() }}⚠️ Lưu ý quan trọng
Phần tiêu đề “⚠️ Lưu ý quan trọng”✅ Checklist - Tự kiểm tra
Phần tiêu đề “✅ Checklist - Tự kiểm tra”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
: Typevàwhere - 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