Hàm phạm vi — khái niệm, các loại và ứng dụng trong Kotlin

Tác giả: IT Sectr Đã đăng: 2026-06-23 Thời gian đọc: 8 phút

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 — năm hàm Kotlin (let, run, with, apply, also) để làm việc trong ngữ cảnh đối tượng
  • Ngữ cảnh qua this — run, with, apply sử dụng this, cho phép gọi phương thức của đối tượng trực tiếp
  • Ngữ cảnh qua it — let và also truyền đối tượng dưới dạng tham số it với tên gọi rõ ràng
  • Trả về đối tượng — apply và also trả về đối tượng ngữ cảnh để xâu chuỗi lời gọi
  • Trả về kết quả — let, run và with trả về biểu thức cuối cùng của lambda

Hàm phạm vi là gì?

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.

So sánh hàm phạm vi: this vs it, trả về đối tượng vs kết quả

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àmTruy cậpTrả vềMở rộng?Kịch bản điển hình
letitkết quả lambdaChuyển đổi, kiểm tra null
runthiskết quả lambdaTính toán với ngữ cảnh
withthiskết quả lambdakhôngNhóm lời gọi
applythisđối tượng ngữ cảnhKhởi tạo thuộc tính
alsoitđối tượng ngữ cảnhTá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 và also: cấu hình với trả về đối tượng

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.

kotlin
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ụ.

kotlin
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 và run: chuyển đổi và tính toán

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.

kotlin
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.

kotlin
// 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: ngữ cảnh không mở rộng

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.

kotlin
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

Nên chọn hàm phạm vi nào để khởi tạo đối tượng?

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 khác run như thế nào?

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.

Khi nào sử dụng with thay vì apply?

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ó thể lồng hàm phạm vi này vào hàm phạm vi khác không?

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.

Hàm phạm vi nào hiệu suất cao nhất?

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

  • Hàm phạm vi — năm hàm inline Kotlin (let, run, with, apply, also) để thực thi mã trong ngữ cảnh đối tượng
  • apply trả về đối tượng ngữ cảnh với truy cập qua this — lý tưởng để khởi tạo thuộc tính
  • also trả về đối tượng ngữ cảnh với truy cập qua it — phù hợp cho ghi log và tác dụng phụ
  • let trả về kết quả lambda với truy cập qua it — nền tảng cho chuyển đổi an toàn với null
  • run trả về kết quả lambda với truy cập qua this — thuận tiện cho tính toán với ngữ cảnh
  • with không phải là hàm mở rộng và trả về kết quả lambda — phù hợp để nhóm lời gọi không xâu chuỗi
  • Sự kết hợp đặc điểm (this/it và trả về đối tượng/kết quả) xác định việc chọn hàm phạm vi cho mỗi tác vụ

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.

Thảo luận dự án

Đọc thêm