🔌 Extension Functions
🎯 Mục tiêu: Hiểu Extension Functions - cách “thêm” methods vào classes có sẵn mà không cần kế thừa hay modify source code.
💡 Extension Functions là gì?
Phần tiêu đề “💡 Extension Functions là gì?”Extension function cho phép “mở rộng” class bằng cách thêm function mới, hoạt động như thể nó là member của class đó.
// Thêm method cho Stringfun String.addExclamation(): String = "$this!"
// Thêm method cho Intfun Int.isEven(): Boolean = this % 2 == 0
fun main() { println("Hello".addExclamation()) // Hello! println(4.isEven()) // true println(5.isEven()) // false}📝 Cú pháp
Phần tiêu đề “📝 Cú pháp”fun ReceiverType.functionName(parameters): ReturnType { // 'this' tham chiếu đến receiver object return this.someOperation()}| Component | Description |
|---|---|
ReceiverType |
Class bạn muốn extend |
functionName |
Tên function mới |
this |
Instance của ReceiverType (implicit) |
🔧 Ví dụ thực tế
Phần tiêu đề “🔧 Ví dụ thực tế”String Extensions
Phần tiêu đề “String Extensions”fun String.isEmail(): Boolean = contains("@") && contains(".") && length > 5
fun String.toTitleCase(): String = split(" ").joinToString(" ") { word -> word.lowercase().replaceFirstChar { it.uppercase() } }
fun String.truncate(maxLength: Int, suffix: String = "..."): String = if (length <= maxLength) this else take(maxLength - suffix.length) + suffix
fun String.removeWhitespace(): String = filterNot { it.isWhitespace() }
fun main() { println("hello world kotlin".toTitleCase()) // Hello World Kotlin println("This is a long text".truncate(10)) // This is... println("h e l l o".removeWhitespace()) // hello}Collection Extensions
Phần tiêu đề “Collection Extensions”fun <T> List<T>.secondOrNull(): T? = getOrNull(1)
fun <T> List<T>.randomOrNull(): T? = if (isEmpty()) null else random()
fun <T> List<T>.chunkedBySize(size: Int): List<List<T>> = chunked(size)
fun <T> MutableList<T>.swap(index1: Int, index2: Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp}
fun main() { val numbers = listOf(1, 2, 3, 4, 5) println(numbers.secondOrNull()) // 2 println(numbers.randomOrNull()) // Random number println(numbers.chunkedBySize(2)) // [[1, 2], [3, 4], [5]]
val mutableList = mutableListOf("a", "b", "c") mutableList.swap(0, 2) println(mutableList) // [c, b, a]}Number Extensions
Phần tiêu đề “Number Extensions”fun Int.squared(): Int = this * thisfun Int.cubed(): Int = this * this * thisfun Double.roundTo(decimals: Int): Double = "%.${decimals}f".format(this).toDouble()
fun Int.ordinal(): String = when { this % 100 in 11..13 -> "${this}th" this % 10 == 1 -> "${this}st" this % 10 == 2 -> "${this}nd" this % 10 == 3 -> "${this}rd" else -> "${this}th"}
fun main() { println(5.squared()) // 25 println(3.cubed()) // 27 println(3.14159.roundTo(2)) // 3.14
println(1.ordinal()) // 1st println(2.ordinal()) // 2nd println(3.ordinal()) // 3rd println(11.ordinal()) // 11th println(21.ordinal()) // 21st}📦 Extension Properties
Phần tiêu đề “📦 Extension Properties”Không chỉ functions, Kotlin cho phép extension properties:
val String.lastChar: Char get() = this[length - 1]
val String.firstChar: Char get() = this[0]
val <T> List<T>.lastIndex: Int get() = size - 1
// Mutable extension propertyvar StringBuilder.lastChar: Char get() = this[length - 1] set(value) { this.setCharAt(length - 1, value) }
fun main() { println("Hello".lastChar) // o println("Hello".firstChar) // H
val sb = StringBuilder("Hello") sb.lastChar = '!' println(sb) // Hell!}🎯 Generic Extensions
Phần tiêu đề “🎯 Generic Extensions”fun <T> T.toSingletonList(): List<T> = listOf(this)
fun <T> T.applyIf(condition: Boolean, block: T.() -> T): T = if (condition) block() else this
fun <T : Comparable<T>> T.coerceInOrNull(range: ClosedRange<T>): T? = if (this in range) this else null
fun main() { val list = "Hello".toSingletonList() println(list) // [Hello]
val number = 5.applyIf(true) { this * 2 } println(number) // 10
println(5.coerceInOrNull(1..10)) // 5 println(15.coerceInOrNull(1..10)) // null}⚠️ Static Resolution
Phần tiêu đề “⚠️ Static Resolution”Member Functions Win
Phần tiêu đề “Member Functions Win”Nếu class đã có member function cùng tên, member sẽ được gọi:
class Example { fun greet() = "Member function"}
fun Example.greet() = "Extension function"
fun main() { println(Example().greet()) // "Member function" ← member wins!}📁 Organizing Extensions
Phần tiêu đề “📁 Organizing Extensions”// ✅ Best practice: Nhóm extensions theo receiver type
package com.example.extensions
fun String.isEmail(): Boolean = /* ... */fun String.toTitleCase(): String = /* ... */
// File: ListExtensions.ktpackage com.example.extensions
fun <T> List<T>.secondOrNull(): T? = /* ... */
// Import và sử dụngimport com.example.extensions.isEmailimport com.example.extensions.toTitleCase// hoặcimport com.example.extensions.*🛠️ Thực hành
Phần tiêu đề “🛠️ Thực hành”Bài tập: Tạo utility extensions
// TODO: Tạo các extensions sau:// - String.isValidPhoneNumber(): Boolean (10-11 digits)// - String.mask(visibleChars: Int): String (mask all except last N chars)// - Int.toOrdinal(): String (1st, 2nd, 3rd, 4th...)// - List<Int>.median(): DoubleLời giải:
fun String.isValidPhoneNumber(): Boolean { val digitsOnly = filter { it.isDigit() } return digitsOnly.length in 10..11}
fun String.mask(visibleChars: Int = 4, maskChar: Char = '*'): String { if (length <= visibleChars) return this val masked = maskChar.toString().repeat(length - visibleChars) return masked + takeLast(visibleChars)}
fun Int.toOrdinal(): String = when { this % 100 in 11..13 -> "${this}th" this % 10 == 1 -> "${this}st" this % 10 == 2 -> "${this}nd" this % 10 == 3 -> "${this}rd" else -> "${this}th"}
fun List<Int>.median(): Double { if (isEmpty()) return 0.0 val sorted = sorted() val middle = size / 2 return if (size % 2 == 0) { (sorted[middle - 1] + sorted[middle]) / 2.0 } else { sorted[middle].toDouble() }}
fun main() { println("0901234567".isValidPhoneNumber()) // true println("090123".isValidPhoneNumber()) // false
println("4532123456789012".mask()) // ************9012 println("1234567890".mask(3, 'X')) // XXXXXXX890
println(1.toOrdinal()) // 1st println(22.toOrdinal()) // 22nd println(111.toOrdinal()) // 111th
println(listOf(1, 3, 5, 7, 9).median()) // 5.0 println(listOf(1, 2, 3, 4, 5, 6).median()) // 3.5}📱 Trong Android
Phần tiêu đề “📱 Trong Android”// View Extensionsfun View.show() { visibility = View.VISIBLE }fun View.hide() { visibility = View.GONE }fun View.invisible() { visibility = View.INVISIBLE }
fun View.setVisibleIf(condition: Boolean) { visibility = if (condition) View.VISIBLE else View.GONE}
// Context Extensionsfun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, message, duration).show()}
fun Context.dpToPx(dp: Int): Int = (dp * resources.displayMetrics.density).toInt()
fun Context.getColorCompat(colorRes: Int): Int = ContextCompat.getColor(this, colorRes)
// Fragment Extensionsfun Fragment.hideKeyboard() { view?.let { activity?.hideKeyboard(it) }}
// EditText Extensionsfun EditText.onTextChanged(action: (String) -> Unit) { addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable?) {} override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { action(s?.toString() ?: "") } })}
// Usagebinding.button.show()binding.progressBar.setVisibleIf(isLoading)toast("Welcome!")binding.editText.onTextChanged { text -> validateInput(text) }⚠️ 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ể:
- Tạo extension functions cho existing classes
- Sử dụng
thisđể tham chiếu receiver - Tạo generic extension functions
- Tạo extension properties
- Hiểu static resolution (không có polymorphism)
- Biết member functions có priority cao hơn extensions
- Tổ chức extensions trong files riêng
Tiếp theo: Inline Functions