Skip to Content
Kotlin📘 Ngôn ngữ Kotlin🔌 Extension Functions

Extension Functions trong Kotlin

🎯 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ì?

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 String fun String.addExclamation(): String = "$this!" // Thêm method cho Int fun Int.isEven(): Boolean = this % 2 == 0 fun main() { println("Hello".addExclamation()) // Hello! println(4.isEven()) // true println(5.isEven()) // false }

📝 Cú pháp

fun ReceiverType.functionName(parameters): ReturnType { // 'this' tham chiếu đến receiver object return this.someOperation() }
ComponentDescription
ReceiverTypeClass bạn muốn extend
functionNameTên function mới
thisInstance của ReceiverType (implicit)

🔧 Ví dụ thực tế

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("[email protected]".isEmail()) // true 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

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

fun Int.squared(): Int = this * this fun Int.cubed(): Int = this * this * this fun 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

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 property var 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! }
⚠️

Extension properties không có backing field: Không thể khởi tạo state, chỉ có computed getters/setters.

// ❌ Lỗi - không có backing field val String.cachedLength: Int = length // ✅ OK - computed property val String.cachedLength: Int get() = length

🎯 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

⚠️

Extensions are resolved STATICALLY, không phải dynamically (polymorphism):

open class Parent class Child : Parent() fun Parent.greet() = "Hello from Parent" fun Child.greet() = "Hello from Child" fun printGreeting(parent: Parent) { println(parent.greet()) } fun main() { printGreeting(Child()) // "Hello from Parent" ❗ Không phải "Hello from Child" }

Extension được chọn dựa trên compile-time type (Parent), không phải runtime type (Child).

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

// ✅ Best practice: Nhóm extensions theo receiver type // File: StringExtensions.kt package com.example.extensions fun String.isEmail(): Boolean = /* ... */ fun String.toTitleCase(): String = /* ... */ // File: ListExtensions.kt package com.example.extensions fun <T> List<T>.secondOrNull(): T? = /* ... */ // Import và sử dụng import com.example.extensions.isEmail import com.example.extensions.toTitleCase // hoặc import com.example.extensions.*

🛠️ 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(): Double

Lờ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

// View Extensions fun 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 Extensions fun 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 Extensions fun Fragment.hideKeyboard() { view?.let { activity?.hideKeyboard(it) } } // EditText Extensions fun 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() ?: "") } }) } // Usage binding.button.show() binding.progressBar.setVisibleIf(isLoading) toast("Welcome!") binding.editText.onTextChanged { text -> validateInput(text) }

⚠️ Lưu ý quan trọng

💡

Khi nào dùng Extension Functions:

  • Utility functions cho existing classes
  • Thêm domain-specific methods
  • Improve readability
  • Không có access đến private members

Khi KHÔNG nên dùng:

  • Cần access private members → dùng member function
  • Override behavior → dùng inheritance
  • Cần polymorphism → dùng interface
⚠️

Extensions không access private members:

class User(private val password: String) // ❌ Không thể access private fun User.showPassword() = password // Compile error!

✅ 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

Last updated on