Lambda là một hàm ẩn danh được biểu diễn dưới dạng biểu thức nhỏ gọn { đối_số -> thân }. Trong Kotlin, lambda là cách chính để truyền hành vi vào các hàm bậc cao, cung cấp cú pháp ngắn gọn mà không cần khai báo một hàm riêng biệt. Theo tài liệu Kotlin (2026), hơn 70% các hàm bậc cao trong các dự án điển hình được gọi với lambda. Lambda cho phép viết mã theo phong cách hàm với tối thiểu nhiễu cú pháp.
Những điểm chính
Lambda (biểu thức lambda) là một hàm ẩn danh được định nghĩa như một biểu thức thay vì một khai báo. Thuật ngữ này bắt nguồn từ phép tính lambda của Alonzo Church (1936). Trong lập trình, lambda là một khối mã có thể được truyền như một giá trị, lưu trữ trong biến và gọi sau đó. Trong Kotlin, lambda luôn được đặt trong dấu ngoặc nhọn { }, với các đối số được phân cách khỏi thân bằng mũi tên ->.
Lambda khác với hàm thông thường ở chỗ không có tên và từ khóa fun. Điều này cho phép khai báo hành vi trực tiếp tại điểm sử dụng mà không làm lộn xộn mã với các khai báo bổ sung. Trình biên dịch Kotlin suy luận kiểu đối số từ ngữ cảnh, vì vậy trong nhiều trường hợp có thể bỏ qua kiểu. Lambda là cú pháp đường bao quanh kiểu hàm (T) -> R: mọi lambda đều có một kiểu hàm tương ứng.
Công dụng chính của lambda trong Kotlin là truyền chúng vào các hàm bậc cao. Thư viện chuẩn sử dụng lambda ở khắp mọi nơi: filter { it > 0 }, map { it.uppercase() }, forEach { println(it) }. Lambda cũng được sử dụng cho bộ lắng nghe sự kiện, callback và bộ cấu hình đối tượng thông qua apply và run, làm cho mã khai báo hơn so với các lớp ẩn danh.
Dạng đầy đủ của một biểu thức lambda trong Kotlin: { đối_số -> thân_lambda }. Các đối số được liệt kê cách nhau bằng dấu phẩy, tiếp theo là mũi tên và thân — một hoặc nhiều biểu thức. Nếu thân chứa nhiều dòng, biểu thức cuối cùng trở thành giá trị trả về. Nếu một hàm nhận lambda làm đối số cuối cùng, nó có thể được đưa ra ngoài dấu ngoặc gọi.
val sum: (Int, Int) -> Int = { a, b -> a + b }
// Kiểu được suy luận từ ngữ cảnh
val sumShort = { a: Int, b: Int -> a + b }
// Gọi Lambda
val result = sum(5, 3) // 8
println(result)
Biến sum được khai báo với kiểu hàm tường minh (Int, Int) -> Int và được khởi tạo với lambda { a, b -> a + b }. Đối với sumShort, kiểu được chỉ định bên trong lambda và kiểu biến được suy luận. Cả hai biến thể đều tương đương. Gọi một lambda không khác gì gọi một hàm thông thường — sử dụng cú pháp sum(5, 3). Lambda không có đối số được viết là { body }, với một đối số — { it -> it * 2 } hoặc đơn giản { it * 2 }.
Khi lambda nhận chính xác một đối số, Kotlin cung cấp tên ngầm định it. Điều này loại bỏ nhu cầu khai báo đối số ở phía bên trái. Nhà phát triển chỉ viết thân lambda, sử dụng it để truy cập tham số duy nhất. Cú pháp này đặc biệt thuận tiện trong chuỗi xử lý bộ sưu tập, nơi it đề cập đến phần tử hiện tại.
val numbers = listOf(1, 2, 3, 4, 5)
// Tên tham số tường minh
val squaredExplicit = numbers.map { n -> n * n }
// it ngầm định
val squared = numbers.map { it * it }
// Lọc với it
val even = numbers.filter { it % 2 == 0 }
println(squared) // [1, 4, 9, 16, 25]
Trong ví dụ, map { it * it } sử dụng it thay vì khai báo n ->. Kotlin tự động gán it cho một tham số lambda duy nhất. Nếu lambda nhận hai hoặc nhiều đối số, it không được tạo — tất cả tham số phải được khai báo tường minh. Để cải thiện khả năng đọc của các lambda lồng nhau, nên đặt tên có ý nghĩa cho các tham số và chỉ sử dụng it trong các vị từ ngắn một dòng.
Trailing lambda là một tính năng cú pháp của Kotlin cho phép đưa đối số lambda cuối cùng ra ngoài dấu ngoặc gọi hàm. Điều này làm cho mã trông giống như các cấu trúc ngôn ngữ tích hợp sẵn. Nếu lambda là đối số duy nhất, dấu ngoặc có thể được bỏ qua hoàn toàn. Cách tiếp cận này được sử dụng tích cực trong thư viện chuẩn và DSL.
// Gọi chuẩn
thread(block = {
println("Xin chào từ luồng")
})
// Phiên bản trailing lambda
thread {
println("Xin chào từ luồng")
}
// lặp với trailing lambda
repeat(3) { index ->
println("Lặp $index")
}
// chạy với lambda nhiều dòng
val config = run {
val host = "localhost"
val port = 8080
"$host:$port"
}
Hàm thread nhận Runnable làm đối số cuối cùng. Nhờ trailing lambda, lambda có thể được truyền bên ngoài dấu ngoặc, trông giống như một khối mã. Hàm repeat nhận số lần lặp và một lambda với chỉ mục. run là một ví dụ về hàm bậc cao nơi lambda nhiều dòng tính toán một giá trị được trả về bởi biểu thức cuối cùng. Dấu ngoặc quanh run() có thể được bỏ qua nếu lambda là đối số duy nhất.
Chụp biến (closure) là khả năng của lambda để truy cập các biến được khai báo trong phạm vi bên ngoài. Trong Kotlin, lambda có thể đọc và sửa đổi biến var, cũng như đọc biến val từ ngữ cảnh xung quanh. Các biến được chụp được lưu trữ cùng với lambda trong suốt vòng đời của nó, cho phép tạo các hàm có trạng thái.
fun makeCounter(): () -> Int {
var count = 0
return {
count++ // số lần chụp
}
}
fun main() {
val counter = makeCounter()
println(counter()) // 0
println(counter()) // 1
println(counter()) // 2
}
Hàm makeCounter khai báo một biến cục bộ count và trả về một lambda { count++ }. Mặc dù makeCounter đã hoàn thành thực thi, lambda được trả về vẫn giữ tham chiếu đến count thông qua bao đóng. Mỗi lần gọi counter() đều tăng và trả về giá trị hiện tại. Cơ chế closure là nền tảng của currying, nhà máy hàm và ghi nhớ. Điều quan trọng cần nhớ là việc chụp các biến có thể thay đổi có thể dẫn đến hành vi không mong muốn trong thực thi đồng thời.
Câu hỏi thường gặp
Lambda không hỗ trợ return vô điều kiện — return không có nhãn sẽ trả quyền điều khiển cho hàm bên ngoài. Để thoát khỏi lambda, hãy sử dụng return@label, trong đó label là tên của hàm bậc cao mà lambda đã được truyền vào.
Hàm ẩn danh được viết là fun(a: Int): Int { return a * 2 } và hỗ trợ return không có nhãn. Lambda { a -> a * 2 } ngắn hơn về cú pháp nhưng yêu cầu return@label để thoát sớm.
Nếu lambda không nhận đối số, mũi tên được bỏ qua: { body() }. Nếu nó nhận một đối số nhưng không sử dụng, hãy viết { _ -> body() } với dấu gạch dưới để bỏ qua tham số.
Có, mọi lambda được truyền vào hàm không inline đều tạo một thể hiện của một lớp ẩn danh. Đối với hàm inline, lambda được nội tuyến tại vị trí gọi mà không tạo đối tượng. Các lambda không chụp được lưu vào bộ nhớ đệm dưới dạng đối tượng đơn thể.
Return trong Kotlin là một toán tử nhảy thuộc về hàm gần nhất được khai báo với fun. Lambda không phải là một hàm (fun), mà là một biểu thức. Do đó, return bên trong lambda không có nhãn được coi là trả về từ hàm bên ngoài bao quanh.
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