Hàm phạm vi trong Kotlin là năm hàm thư viện chuẩn (let, run, with, apply, also) thực thi một khối mã trong ngữ cảnh của một đối tượng. Chúng khác nhau ở cách truy cập đối tượng (qua it hoặc this) và giá trị trả về (đối tượng ngữ cảnh hoặc kết quả lambda). Theo tài liệu Kotlin (2026), việc chọn đúng hàm phạm vi giúp giảm mã mẫu từ 25–40%. Hàm phạm vi giúp viết mã khởi tạo, cấu hình và chuyển đổi đối tượng theo phong cách khai báo.
Các điểm chính
Hàm phạm vi là một tập hợp năm hàm (let, run, with, apply, also) được tích hợp trong thư viện chuẩn Kotlin. Mỗi hàm nhận một lambda và tạm thời thay đổi phạm vi để mã bên trong lambda thực thi trong ngữ cảnh của một đối tượng cụ thể. Điều này loại bỏ việc lặp lại tên đối tượng cho nhiều lời gọi và nhóm các thao tác liên quan thành một khối duy nhất.
Hai đặc điểm chính phân biệt các hàm phạm vi: cách truy cập đối tượng ngữ cảnh (qua this hoặc qua it) và giá trị trả về (bản thân đối tượng ngữ cảnh hoặc kết quả lambda). Truy cập qua this làm cho đối tượng trở thành bộ nhận ngầm — các phương thức và thuộc tính được gọi mà không có tiền tố. Truy cập qua it truyền đối tượng dưới dạng tham số rõ ràng của lambda. Trả về đối tượng cho phép xâu chuỗi, trả về kết quả cho phép gán giá trị đã chuyển đổi.
Trong lịch sử, các hàm phạm vi xuất hiện như một sự thay thế cho mẫu Builder cổ điển và các biến tạm thời để khởi tạo. Thay vì tạo một lớp builder riêng biệt, nhà phát triển viết apply { thuộc tính1 = ...; thuộc tính2 = ... }. Điều này giảm lượng mã và làm cho việc khởi tạo dễ đọc hơn. Trong hệ sinh thái Kotlin, các hàm phạm vi được sử dụng trong tất cả các framework và thư viện như một thành ngữ ngôn ngữ tiêu chuẩn.
Việc chọn hàm phạm vi phù hợp được xác định bởi hai câu hỏi: bạn muốn sử dụng this hay it để truy cập đối tượng, và bạn muốn trả về chính đối tượng hay kết quả lambda. Sự kết hợp của hai đặc điểm nhị phân tạo ra bốn tùy chọn khả thi, và with chiếm một vị trí đặc biệt là hàm duy nhất không phải là phần mở rộng.
| Hàm | Truy cập | Trả về | Mở rộng? | Kịch bản điển hình |
|---|---|---|---|---|
| let | it | kết quả lambda | có | Chuyển đổi, kiểm tra null |
| run | this | kết quả lambda | có | Tính toán với ngữ cảnh |
| with | this | kết quả lambda | không | Nhóm lời gọi |
| apply | this | đối tượng ngữ cảnh | có | Khởi tạo thuộc tính |
| also | it | đối tượng ngữ cảnh | có | Tác dụng phụ, ghi log |
Để cấu hình đối tượng mà không trả về giá trị mới, hãy sử dụng apply (truy cập qua this) hoặc also (truy cập qua it). Để tính toán giá trị mới dựa trên đối tượng, hãy sử dụng let (truy cập qua it) hoặc run (truy cập qua this). Để nhóm lời gọi mà không xâu chuỗi, hãy sử dụng with. Tuân theo sơ đồ này làm cho mã có thể dự đoán được đối với các nhà phát triển khác quen thuộc với Kotlin.
apply là hàm phạm vi trả về đối tượng ngữ cảnh và cung cấp quyền truy cập vào nó qua this. Đây là công cụ lý tưởng để khởi tạo các thuộc tính của đối tượng sau khi tạo. Bên trong khối apply, các thuộc tính có thể được thiết lập mà không cần lặp lại tên đối tượng. Phương thức trả về chính đối tượng, cho phép nhúng apply vào chuỗi hoặc sử dụng trong bộ khởi tạo.
data class ServerConfig(
var host: String = "localhost",
var port: Int = 8080,
var useTls: Boolean = false
)
val config = ServerConfig().apply {
host = "api.example.com"
port = 443
useTls = true
}
Trong ví dụ, apply cấu hình ServerConfig bằng cách truy cập trực tiếp các thuộc tính qua this (this được lược bỏ). Nếu không có apply, bạn phải viết config.host = ..., config.port = ... — ba tham chiếu lặp lại đến cùng một biến. Việc trả về đối tượng cho phép lưu kết quả cấu hình trong val config. also, không giống như apply, truyền đối tượng qua it và phù hợp cho các tác dụng phụ.
fun saveUser(user: User) {
validate(user).also { result ->
println("Kết quả xác thực: $result")
}
val savedUser = user.also {
log("Saving user ${it.id}")
database.save(it)
}
}
Trong ví dụ, also được sử dụng để ghi log — trong khi trả về đối tượng gốc, nó thực hiện một tác dụng phụ (xuất thông báo) mà không sửa đổi đối tượng. Đây là một chuỗi: validate(user) trả về User, also ghi log kết quả và chuyển người dùng đi tiếp. also cũng hữu ích để thêm phần tử vào bộ sưu tập hoặc gỡ lỗi ở giữa chuỗi chuyển đổi.
let là hàm phạm vi trả về kết quả lambda và truyền ngữ cảnh qua it. Đây là hàm chính cho các chuyển đổi an toàn với null: nếu đối tượng là nullable, sự kết hợp ?.let { } sẽ thực thi khối chỉ cho các giá trị không null. let cũng được sử dụng để giới hạn phạm vi của biến tạm thời và cho các chuyển đổi giống map từ kiểu này sang kiểu khác.
fun findUser(id: Int): User?
val displayName = findUser(42)?.let { user ->
"${user.name} (${user.email})"
} ?: "Unknown user"
// let với chuyển đổi bộ sưu tập
val numbers = listOf("1", "2", "3")
val parsed = numbers.firstOrNull()?.let {
it.toIntOrNull()
} ?: 0
Trong ví dụ đầu tiên, let được gọi qua ?. — khối chỉ thực thi nếu findUser trả về giá trị không null. Lambda nhận user qua it (có thể đổi tên cho rõ ràng) và trả về một chuỗi đã định dạng. Nếu không tìm thấy người dùng, toán tử Elvis ?: cung cấp giá trị mặc định. Trong ví dụ thứ hai, let chuyển đổi chuỗi thành số với khả năng null.
run là một hàm tương tự let với truy cập qua this, trả về kết quả lambda. run thuận tiện cho các tính toán yêu cầu ngữ cảnh đối tượng nhưng trả về giá trị mới. Gọi run() mà không có đối tượng (như run { ... }) tạo ra một phạm vi tạm thời cho các biến. with là một hàm không mở rộng với truy cập qua this, phù hợp để nhóm các lời gọi mà không xâu chuỗi.
// run như tính toán dựa trên ngữ cảnh
val transformed = listOf(1, 2, 3).run {
filter { it > 1 }.map { it * 10 }
}
println(transformed) // [20, 30]
// with để nhóm lời gọi
val info = with(StringBuilder()) {
append("Name: ")
append("Alice")
toString()
}
Trong ví dụ, run trên danh sách trả về kết quả của việc lọc và ánh xạ — bản thân sự chuyển đổi, không phải danh sách. with nhận StringBuilder làm đối số (không qua dấu chấm) và bên trong làm việc với this: append, append, toString. Kết quả của with là chuỗi info. Sự lựa chọn giữa run và with thường mang tính chủ quan, nhưng with được ưa thích hơn khi đối tượng được truyền từ nơi khác mà không xâu chuỗi.
with khác với các hàm phạm vi khác ở chỗ nó không phải là hàm mở rộng. Nó nhận đối tượng làm đối số đầu tiên và lambda làm đối số thứ hai. Bên trong lambda, đối tượng có thể truy cập qua this. with thuận tiện để nhóm các lời gọi phương thức đối tượng mà không lưu kết quả hoặc khi bạn cần tạm thời “đi vào” một ngữ cảnh để đọc nhiều thuộc tính.
val user = User("Alice", "alice@example.com", "Admin")
val summary = with(user) {
"User $name has role $role and email $email"
}
println(summary)
// with để khởi tạo thành phần UI
with(TextView(context)) {
text = "Hello"
textSize = 18.0f
setTextColor(Color.BLUE)
}
Trong ví dụ đầu tiên, with tạo chuỗi summary bằng cách truy cập trực tiếp các thuộc tính của user qua this. Nếu không có with, bạn phải viết “User ${user.name} has role ${user.role}” — lặp lại user. Trong ví dụ thứ hai, with nhóm cấu hình TextView: tất cả các phương thức và thuộc tính được gọi trên một đối tượng mà không lặp lại. Nhược điểm của with — không thể sử dụng trong chuỗi vì nó không phải là hàm mở rộng và không trả về đối tượng ngữ cảnh.
Các câu hỏi thường gặp
Sử dụng apply — nó trả về đối tượng và cung cấp quyền truy cập qua this, cho phép thiết lập thuộc tính mà không lặp lại tên đối tượng. also phù hợp nếu bạn cần tham số it rõ ràng để phân biệt với các thuộc tính của đối tượng.
let truyền ngữ cảnh qua it (tham số rõ ràng), run — qua this (bộ nhận ngầm). let thuận tiện cho kiểm tra null và chuyển đổi, run — cho tính toán với ngữ cảnh. Cả hai đều trả về kết quả lambda.
with không phải là hàm mở rộng — nó nhận đối tượng làm đối số. Sử dụng with để nhóm lời gọi khi không cần xâu chuỗi. apply là hàm mở rộng trả về đối tượng, thuận tiện trong bộ khởi tạo và mẫu builder.
Có, nhưng hãy cẩn thận với this — this lồng nhau gây nhầm lẫn. Sử dụng let hoặc also với tên rõ ràng cho các ngữ cảnh lồng nhau để tránh mơ hồ khi tham chiếu đến đối tượng bên ngoài.
Tất cả các hàm phạm vi đều là inline, do đó chúng không tạo ra chi phí từ các lớp ẩn danh. Sự khác biệt về hiệu suất giữa chúng là không đáng kể. Việc lựa chọn nên dựa trên khả năng đọc và quy ước, không phải tố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