extension function — một cơ chế của Kotlin cho phép thêm các phương thức mới vào các lớp hiện có mà không cần kế thừa và không cần sửa đổi mã nguồn. Hàm được khai báo với tiền tố dưới dạng kiểu tiếp nhận và được gọi như một phương thức thông thường của kiểu đó. Theo Kotlin Documentation, 2025, extension functions được biên dịch thành các phương thức tĩnh với tham số đầu tiên là bộ tiếp nhận, đảm bảo không có overhead trong thời gian chạy so với các lệnh gọi thông thường.
Những điểm chính
Extension function là một hàm được khai báo bên ngoài một lớp nhưng được gọi như phương thức của nó. Nó nhận một kiểu tiếp nhận mà phương thức mới sẽ được thêm vào. Bên trong hàm, từ khóa this tham chiếu đến thể hiện của kiểu tiếp nhận. Cách tiếp cận này cho phép mở rộng các lớp final, lớp thư viện bên thứ ba và thậm chí cả kiểu nguyên thủy mà không cần tạo wrapper.
Không giống như Java, nơi để thêm một phương thức vào lớp hiện có cần kế thừa hoặc một lớp tiện ích tĩnh, Kotlin cung cấp một giải pháp thanh lịch không có boilerplate. Theo một nghiên cứu của Kotlin Foundation (2024), extension functions nằm trong số năm tính năng được sử dụng nhiều nhất của ngôn ngữ — 78% nhà phát triển Kotlin sử dụng chúng trong công việc hàng ngày.
Trình biên dịch Kotlin dịch extension function thành một phương thức tĩnh, nơi thể hiện của bộ tiếp nhận được truyền như đối số đầu tiên. Điều này có nghĩa là extension functions không sửa đổi bytecode của lớp mục tiêu và không phá vỡ tính đóng gói — chỉ có các trường và phương thức công khai của bộ tiếp nhận mới có thể truy cập được.
Sử dụng extension functions để viết các hàm tiện ích sạch sẽ, gắn kết logic với một kiểu cụ thể nhưng không thể thêm vào mã nguồn của nó.
Cú pháp cơ bản của extension function: tên kiểu tiếp nhận, dấu chấm, tên hàm, sau đó là tham số và kiểu trả về. Bên trong hàm, bộ tiếp nhận có thể truy cập qua this.
// Extension function for String
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// Called as regular String method
val result = "user@example.com".isEmail() // true
Trong ví dụ này, isEmail() trở nên khả dụng cho tất cả chuỗi. Bên trong hàm, this tham chiếu đến chính chuỗi mà phương thức được gọi trên đó. Kotlin cho phép bỏ qua this trong hầu hết các trường hợp — bạn có thể chỉ cần viết contains("@") thay vì this.contains("@").
Extension functions có thể chấp nhận các tham số bổ sung và trả về giá trị của bất kỳ kiểu nào. Điều này làm cho chúng trở thành các hàm chính thức, không chỉ là đường cú pháp.
fun List<Int>.defaultIfEmpty(default: Int): List<Int> {
if (this.isEmpty()) return listOf(default)
return this
}
val populated = listOf(1, 2).defaultIfEmpty(0)
val empty = listOf<Int>().defaultIfEmpty(0)
Tham số default xác định giá trị trả về cho danh sách trống. Extension function có thể là generic — kiểu List
Extension functions trong Kotlin sử dụng phân phối tĩnh, không phải phân phối ảo. Đây là điểm khác biệt chính so với các phương thức lớp thông thường. Extension function nào được gọi được xác định tại thời điểm biên dịch bởi kiểu tĩnh của biến, không phải bởi kiểu thực tế trong thời gian chạy.
open class Animal
class Dog : Animal()
fun Animal.speak() = "Animal sound"
fun Dog.speak() = "Woof"
fun test() {
val animal: Animal = Dog()
println(animal.speak()) // "Animal sound" — static type Animal
val dog: Dog = Dog()
println(dog.speak()) // "Woof" — static type Dog
}
Mặc dù biến animal trỏ đến một thể hiện của Dog, extension function cho Animal được gọi vì kiểu tĩnh của biến là Animal. Nếu speak() là một phương thức ảo của lớp, Dog.speak() sẽ được gọi. Hành vi này cần được xem xét khi thiết kế API với extension functions.
Extension functions không phải là thành viên của lớp — chúng là các hàm thông thường cần import để sử dụng trong tệp khác. Kotlin cung cấp hai tùy chọn import: theo tên hàm hoặc với đổi tên.
// Import by name
import com.example.extensions.isEmail
// Import with alias (to resolve conflicts)
import com.example.extensions.isEmail as isValidEmail
// Call after import
val valid = "test@test.com".isEmail()
Đổi tên qua as hữu ích cho xung đột tên — ví dụ: nếu hai thư viện cung cấp extension function cùng tên cho cùng một kiểu. Trong trường hợp này, bạn có thể import một trong số chúng với tên khác và gọi nó bằng tên mới.
Extension functions có thể được khai báo ở các cấp độ khác nhau:
| Cấp độ | Khả năng hiển thị | Ví dụ |
|---|---|---|
| Top-level | Toàn bộ dự án sau khi import | fun String.isEmail() |
| Member extension | Bên trong lớp sở hữu | class A { fun B.ext() } |
| Local | Bên trong một hàm | fun test() { fun String.ext() } |
Member extension functions là trường hợp đặc biệt khi extension function được khai báo bên trong một lớp khác. Trong trường hợp này, cả bộ tiếp nhận (this của hàm) và các thành viên của lớp bên ngoài đều có thể truy cập được bên trong extension function.
Nếu một lớp có phương thức cùng chữ ký với extension function, thành viên lớp luôn được gọi. Extension function không bao giờ ghi đè phương thức lớp — đây là quyết định kiến trúc để ngăn chặn việc ghi đè ngẫu nhiên.
class User {
fun greet() = "Hello from class"
}
fun User.greet() = "Hello from extension"
fun main() {
val user = User()
println(user.greet()) // "Hello from class"
}
Ngay cả khi extension function được định nghĩa sau và có cùng chữ ký, trình biên dịch sẽ chọn phương thức của lớp. Trình biên dịch Kotlin đưa ra cảnh báo khi phát hiện tình huống này. Cách duy nhất để gọi extension function khi phương thức lớp tồn tại là gọi nó như một hàm thông thường: greet(user).
Trong phát triển Android, extension functions đã trở thành công cụ tiêu chuẩn để làm việc với View, Context và fragment. Thư viện Android KTX được xây dựng trên extension functions, cung cấp các wrapper tiện lợi cho API Android.
// Extension function for working with View
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
// Extension function for Context — toast
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// Extension function as DSL builder
fun String.colorFormat(color: String): String =
"$color$this\u001b[0m"
Extension function toast() làm cho các lệnh gọi phụ thuộc Context trở nên ngắn gọn: thay vì viết Toast.makeText(context, message, length).show(), chỉ cần viết context.toast("Văn bản"). Điều này giảm boilerplate và làm cho mã dễ đọc hơn.
Câu hỏi thường gặp
Không, extension functions sử dụng phân phối tĩnh. Nếu bạn khai báo extension function cho lớp cơ sở và cùng một hàm cho lớp con, hàm nào được gọi được xác định bởi kiểu tĩnh của biến tại thời điểm biên dịch, không phải bởi kiểu thực tế trong thời gian chạy.
Bạn có thể khai báo extension functions cho kiểu tiếp nhận nullable: fun String?.isNullOrEmail(). Bên trong hàm như vậy, this có thể là null, vì vậy bạn phải sử dụng toán tử gọi an toàn hoặc kiểm tra null rõ ràng.
Không, extension functions được biên dịch thành các phương thức tĩnh. Ở cấp độ bytecode, gọi extension function giống hệt như gọi phương thức tĩnh với bộ tiếp nhận là tham số đầu tiên. Không có phản chiếu hoặc phân phối động nào liên quan.
Có, extension properties hoạt động tương tự extension functions nhưng không thể lưu trữ trạng thái — chỉ có getter và setter. Ví dụ: val List
Trong Java bạn viết StringUtils.isEmail(str), còn trong Kotlin bạn viết str.isEmail(). Sự khác biệt không chỉ về cú pháp: extension functions hỗ trợ tự động hoàn thành trong IDE, cải thiện khả năng đọc của chuỗi gọi và cho phép IDE đề xuất các hàm liên quan cho một kiểu cụ thể. Java không có những khả năng này.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm