Higher-order function là một hàm nhận một hàm khác làm tham số hoặc trả về một hàm dưới dạng kết quả. Trong Kotlin, các hàm là công dân hạng nhất: chúng có thể được lưu trữ trong biến, truyền dưới dạng đối số và được trả về. Theo tài liệu Kotlin (2026), higher-order functions giảm trùng lặp mã trung bình 30% so với các phương pháp mệnh lệnh. Higher-order function là một khái niệm cơ bản của phong cách hàm trong phát triển hiện đại.
Các điểm chính
Higher-order function là một hàm có ít nhất một trong hai đặc điểm: nhận một hàm khác làm đối số hoặc trả về một hàm dưới dạng kết quả. Trong toán học, các hàm như vậy được gọi là phiếm hàm hoặc toán tử. Trong lập trình, chúng xuất hiện trong Lisp năm 1958 và trở thành phần bắt buộc của tất cả các ngôn ngữ hiện đại — JavaScript, Python, Swift, Kotlin, Scala và Haskell. Higher-order function cho phép trừu tượng hóa khỏi một thao tác cụ thể và truyền hành vi dưới dạng giá trị.
Sự khác biệt chính giữa higher-order function và hàm thông thường là sự hiện diện của tham số với kiểu hàm hoặc giá trị hàm được trả về. Trong Kotlin, kiểu hàm được viết dưới dạng (ParamType) -> ReturnType. Ví dụ, kiểu (Int) -> String có nghĩa là một hàm nhận Int và trả về String. Kiểu () -> Unit biểu thị một hàm không có tham số và không trả về giá trị hữu ích. Chính hệ thống kiểu này làm cho higher-order functions an toàn về kiểu.
Trái ngược với higher-order function là first-class function (hàm hạng nhất). First-class function có nghĩa là một hàm có thể được sử dụng như bất kỳ giá trị nào khác: gán cho biến, lưu trữ trong bộ sưu tập, truyền dưới dạng đối số. Higher-order function là một hàm sử dụng first-class functions để nhận hoặc trả về. Kotlin hỗ trợ cả hai khái niệm ở cấp độ ngôn ngữ mà không cần thư viện bổ sung.
Trong Kotlin, bất kỳ hàm nào có ít nhất một tham số được khai báo với kiểu hàm hoặc kiểu trả về là kiểu hàm thì tự động được coi là higher-order function. Trình biên dịch không yêu cầu chú thích đặc biệt — chỉ cần chỉ định (T) -> R trong chữ ký. Khi gọi một hàm như vậy, đối số có thể là một biểu thức lambda, một tham chiếu đến hàm hiện có qua ::, hoặc một giá trị hàm được lưu trữ trong biến.
fun operate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
fun main() {
val sum = operate(10, 20) { x, y -> x + y }
println(sum) // 30
}
Trong đoạn mã, hàm operate nhận hai số nguyên và một tham số op thuộc kiểu (Int, Int) -> Int. Thân hàm gọi thao tác được truyền vào trong một dòng duy nhất. Trong main, lời gọi operate truyền lambda { x, y -> x + y } — Kotlin đặt nó sau dấu ngoặc nhờ cú pháp trailing lambda. Nếu lambda là đối số cuối cùng, nó có thể được đưa ra ngoài dấu ngoặc hoàn toàn, cải thiện khả năng đọc của chuỗi lời gọi.
Bên trong, Kotlin biên dịch higher-order functions thông qua giao diện Function (Function2 cho hai tham số). Mỗi lambda biến thành một lớp ẩn danh thực thi giao diện FunctionN tương ứng. Điều này có nghĩa là việc tạo lambda kéo theo việc cấp phát đối tượng cho mỗi thao tác. Để giảm chi phí, Kotlin hỗ trợ các hàm inline, thay thế thân của higher-order function tại vị trí gọi, loại bỏ việc tạo lớp ẩn danh.
Truyền một hàm dưới dạng đối số là mẫu phổ biến nhất khi sử dụng higher-order functions. Thay vì tạo một hệ phân cấp lớp với một phương thức đa hình, nhà phát triển truyền hành vi mong muốn trực tiếp đến điểm sử dụng. Điều này thực hiện nguyên tắc Mở/Đóng mà không cần kế thừa: một thao tác mới được thêm vào dưới dạng lambda mới, không phải là một lớp con mới.
Đối với các hàm có thể null, Kotlin sử dụng kiểu ((T) -> R)? với dấu chấm hỏi sau dấu ngoặc. Một hàm như vậy chỉ có thể được gọi sau khi kiểm tra null hoặc thông qua toán tử ?.invoke(). Trong trường hợp này, chữ ký của higher-order function chỉ rõ rằng việc truyền hàm là tùy chọn — mã gọi có thể bỏ qua đối số. Điều này hữu ích cho các callback và trình xử lý sự kiện với hành vi tùy chọn.
fun <T> List<T>.customFilter(
predicate: (T) -> Boolean
): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (predicate(item)) result.add(item)
}
return result
}
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val even = numbers.customFilter { it % 2 == 0 }
println(even) // [2, 4]
}
Hàm mở rộng customFilter nhận tham số predicate thuộc kiểu hàm (T) -> Boolean. Bên trong vòng lặp, nó gọi predicate cho từng phần tử và thu thập các phần tử phù hợp. Lời gọi trong main truyền lambda { it % 2 == 0 }, trong đó it là tên ngầm định của tham số duy nhất của lambda. Nhờ higher-order functions, logic lọc hoàn toàn tách biệt khỏi cơ chế duyệt bộ sưu tập.
Một higher-order function có thể trả về một hàm — mẫu này được gọi là nhà máy hàm hoặc trình tạo hành vi. Hàm được trả về có thể chụp (closure) các biến từ phạm vi bên ngoài, giữ chúng giữa các lần gọi. Điều này cho phép tạo các trình xử lý có thể cấu hình và các thao tác chuyên biệt dựa trên các mẫu phổ biến.
Khi trả về một hàm, Kotlin suy ra kiểu hàm ((T) -> R)? được trả về từ chữ ký. Trình biên dịch kiểm tra rằng tất cả các biểu thức return trong thân hàm trả về các giá trị hàm tương thích. Các biến được chụp được lưu trữ trong đối tượng lambda và có thể truy cập miễn là có tham chiếu đến nó. Đây là một cơ chế mạnh mẽ nhưng cần chú ý đến quản lý bộ nhớ.
fun makeMultiplier(factor: Int): (Int) -> Int {
return { x -> x * factor }
}
fun main() {
val double = makeMultiplier(2)
val triple = makeMultiplier(3)
println(double(5)) // 10
println(triple(5)) // 15
}
Hàm makeMultiplier nhận một hệ số và trả về lambda { x -> x * factor }, trong đó factor được chụp từ phạm vi bên ngoài (closure). Mỗi lần gọi makeMultiplier tạo một hàm mới với giá trị factor riêng. Các biến double và triple lưu trữ các hàm được trả về và có thể được gọi nhiều lần. Mẫu này được sử dụng rộng rãi trong cấu hình HTTP client, decorator và middleware.
Sự kết hợp giữa higher-order functions và lambda cho phép xây dựng các chuỗi thao tác biểu cảm mà không cần biến trung gian. Thư viện chuẩn Kotlin chứa hàng chục higher-order functions: let, run, apply, also, filter, map, flatMap, fold, reduce, forEach, groupBy và nhiều hàm khác. Mỗi hàm nhận một lambda và thực hiện biến đổi dữ liệu với nó.
data class User(val name: String, val age: Int)
fun main() {
val users = listOf(
User("Alice", 25),
User("Bob", 17),
User("Charlie", 30)
)
val result = users
.filter { it.age >= 18 }
.map { it.name.uppercase() }
.sorted()
println(result) // [ALICE, CHARLIE]
}
Trong ví dụ, một chuỗi ba higher-order functions xử lý danh sách người dùng. filter nhận một vị từ, chỉ giữ lại người trưởng thành. map biến đổi mỗi người dùng thành tên in hoa. sorted sắp xếp kết quả theo thứ tự tăng dần. Mỗi thao tác nhận một lambda, và Kotlin đảm bảo an toàn kiểu ở mọi giai đoạn. Nếu không có higher-order functions, chúng ta phải viết vòng lặp với if, danh sách tạm thời và sắp xếp thủ công.
Câu hỏi thường gặp
Higher-order function nhận một hàm khác làm tham số hoặc trả về một hàm. Hàm thông thường chỉ làm việc với dữ liệu — số, chuỗi, đối tượng. Higher-order function làm việc với hành vi, truyền logic dưới dạng đối số.
Có, bộ điều chỉnh inline loại bỏ chi phí tạo lớp ẩn danh cho lambda. Bằng cách sử dụng crossinline hoặc noinline, bạn có thể kiểm soát lambda nào được inline và lambda nào vẫn là đối tượng.
Hàm không có tham số và không có giá trị trả về có kiểu () -> Unit. Hàm có một tham số kiểu T và trả về R được viết là (T) -> R. Đối với hai tham số — (T, U) -> R, và cứ như vậy lên đến 22 đối số.
Lambda là ký pháp ngắn gọn { args -> body }, hàm ẩn danh là fun(args): ReturnType { body }. Lambda không thể có return mà không có nhãn, hàm ẩn danh thì có thể. Cả hai đều có thể được truyền vào higher-order function.
Tránh higher-order functions trong các vòng lặp nóng mà không có inline — mỗi lambda tạo một đối tượng. Đối với mã quan trọng về hiệu suất, hãy sử dụng inline fun. Cũng không nên lạm dụng lambda lồng nhau sâu — điều này làm giảm khả năng đọc.
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