Extension property trong Kotlin — định nghĩa, cú pháp và sử dụng

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

extension property — một cơ chế Kotlin cho phép thêm thuộc tính mới vào các lớp hiện có mà không cần kế thừa và không sửa đổi mã nguồn. Không giống như extension functions, extension properties không thể lưu trạng thái — chúng chỉ được khai báo với getter và tùy chọn setter, vì không có backing field. Theo Kotlin Documentation, 2025, extension properties được biên dịch thành phương thức getter và setter tĩnh với receiver là tham số đầu tiên.

Các điểm chính

  • Extension property — thuộc tính với receiver type, truy cập qua cú pháp thuộc tính Kotlin
  • Không có backing field — extension property không thể lưu trạng thái, chỉ tính toán
  • Getter bắt buộc, setter tùy chọn — được khai báo như extension functions thông thường
  • Kiểu nullable được hỗ trợ: receiver có thể nullable với kiểm tra nội bộ
  • Extension property có thể thay đổi — chỉ khi khai báo là var với getter và setter

Extension property trong Kotlin là gì?

Extension property là một cấu trúc cú pháp trong Kotlin thêm thuộc tính vào kiểu hiện có mà không thay đổi khai báo của nó. Thuộc tính được khai báo với receiver type và phải chứa getter. Sự khác biệt chính so với thuộc tính thông thường là không có backing field: extension property không thể lưu dữ liệu, chỉ tính toán dựa trên đối tượng receiver.

Theo Khảo sát Kotlin Foundation (2024), extension properties ít phổ biến hơn extension functions — khoảng 45% nhà phát triển sử dụng chúng thường xuyên. Điều này do hạn chế không có trạng thái, thu hẹp phạm vi ứng dụng. Tuy nhiên, đối với các thuộc tính tính toán gắn liền logic với một kiểu, extension properties là lựa chọn ngắn gọn nhất.

Extension properties được biên dịch thành cặp phương thức getter và setter tĩnh. Ở cấp độ bytecode, không có sự khác biệt giữa gọi extension property và gọi extension function — cả hai đều trở thành phương thức tĩnh với tham số receiver. Theo JetBrains (Kotlin Docs, 2025), không có chi phí nào cả.

Sử dụng extension properties cho các giá trị tính toán ngắn cần trông giống thuộc tính hơn là gọi phương thức — điều này cải thiện khả năng đọc mã và tuân theo Nguyên tắc Truy cập Đồng nhất.

Cú pháp extension property: val và var

Để khai báo extension property, sử dụng cú pháp tương tự thuộc tính thông thường, nhưng với tiền tố receiver type. val khai báo extension property chỉ đọc với getter bắt buộc, var khai báo loại có thể thay đổi với getter và setter tùy chọn.

kotlin
// Extension property chỉ đọc
val String.isEmail: Boolean
    get() = this.contains("@") && this.contains(".")

// Gọi
val valid = "test@test.com".isEmail

Lưu ý: extension property được gọi không có dấu ngoặc — str.isEmail, không phải str.isEmail(). Đây là sự khác biệt chính giữa extension property và extension function: thuộc tính trông giống một trường, mặc dù thực tế được tính qua getter.

Extension property tổng quát

Extension properties có thể tổng quát — receiver có thể sử dụng tham số kiểu tổng quát. Điều này cho phép tạo thuộc tính phổ quát hoạt động với bất kỳ kiểu tập hợp nào.

kotlin
val List<T>.secondOrNull: T?
    get() = if (size >= 2) this[1] else null

val items = listOf("a", "b", "c")
val second = items.secondOrNull // "b"

Thuộc tính secondOrNull hoạt động cho mọi kiểu T, trả về phần tử thứ hai của danh sách hoặc null nếu có ít hơn hai phần tử. Đây là ví dụ điển hình khi extension property phù hợp hơn hàm — truy cập nó trông giống như đọc một trường.

Tại sao extension property không thể lưu trạng thái

Extension property không thể có backing field vì nó không được thêm vào siêu dữ liệu của lớp — nó chỉ tồn tại như một cặp hàm getter/setter tĩnh. Backing field (từ khóa field trong Kotlin) là trường nội bộ của lớp lưu giá trị thuộc tính. Extension property không có quyền truy cập vào cấu trúc nội bộ của lớp.

kotlin
// ❌ LỖI: extension property không thể có backing field
var String.cachedValue: String
    get() = "computed"
    set(value) {
        field = value // trường không thể truy cập!
    }

// ✅ ĐÚNG: sử dụng bộ nhớ ngoài
val cache = MutableMap<String, String>()

var String.cachedValue: String
    get() = cache[this] ?: ""
    set(value) { cache[this] = value }

Một Map bên ngoài trong ví dụ giải quyết vấn đề lưu trữ nhưng tạo ra vấn đề khác — rò rỉ bộ nhớ. Các giá trị thu được qua extension property tồn tại trong Map mãi mãi nếu không được dọn dẹp. Hạn chế này khiến extension properties không phù hợp cho lưu trữ đệm hoặc lưu dữ liệu tạm thời.

Để lưu trữ đệm, nên sử dụng WeakHashMap hoặc cơ chế tự động dọn dẹp. JetBrains khuyến nghị tránh sử dụng var extension properties với bộ nhớ ngoài trong mã sản xuất nếu không quản lý vòng đời cẩn thận.

Extension property vs extension function: khi nào chọn gì

Lựa chọn giữa extension property và extension function phụ thuộc vào ngữ nghĩa: thuộc tính mô tả đặc điểm của đối tượng, trong khi hàm mô tả hành động. Nguyên tắc Truy cập Đồng nhất nêu rõ: khách hàng không cần biết giá trị được tính toán hay lưu trữ. Nếu giá trị có thể biểu diễn như một đặc điểm (độ dài, kích thước, trạng thái) — hãy dùng thuộc tính.

Tiêu chíExtension propertyExtension function
Cách gọiKhông có ngoặc: obj.propertyCó ngoặc: obj.function()
Ngữ nghĩaĐặc điểm, thuộc tínhHành động, thao tác
Backing fieldKhông hỗ trợKhông áp dụng
Tham sốChỉ getter/setterBất kỳ tham số nào
Hiệu suấtGiống nhau (phương thức tĩnh)Giống nhau (phương thức tĩnh)
Ví dụtext.lengthtext.isEmail()

Quy tắc đơn giản: nếu thao tác nhận tham số — dùng extension function. Nếu là giá trị tính toán đơn giản không tham số — dùng extension property. Theo Hướng dẫn Kiến trúc Android (Google, 2025), nên ưu tiên extension properties cho truy cập dữ liệu và extension functions cho thao tác có tác dụng phụ.

Extension property có thể thay đổi với var và setter

Extension property với từ khóa var hỗ trợ setter, nhưng không có khả năng lưu giá trị — setter thường thực hiện tác dụng phụ hoặc lưu dữ liệu vào bộ nhớ ngoài. Cú pháp tương tự thuộc tính lớp có thể thay đổi.

kotlin
// Extension property có thể thay đổi với setter
var StringBuilder.lastChar: Char
    get() = this[length - 1]
    set(value) {
        this.setCharAt(length - 1, value)
    }

val sb = StringBuilder("Kotlin")
println(sb.lastChar) // n
sb.lastChar = '!'
println(sb) // Kotli!

Thuộc tính lastChar là ví dụ kinh điển từ tài liệu Kotlin. Getter trả về ký tự cuối của StringBuilder, setter thay thế nó bằng giá trị mới. Lưu ý: trạng thái được lưu trong chính StringBuilder (qua setCharAt), không phải trường riêng — đây là cách dùng đúng của extension property.

Ví dụ thực tế về extension properties

Trong các dự án thực tế, extension properties thường được dùng để đơn giản hóa truy cập dữ liệu tập hợp, tính kích thước hoặc trạng thái phần tử UI và tạo API tiện lợi trên các lớp hiện có. Thư viện chuẩn Kotlin tích cực sử dụng cơ chế này: size, indices, lastIndex cho tập hợp là extension properties.

kotlin
// Extension properties cho tập hợp
val List<Int>.sumFast: Int
    get() = fold(0) { acc, i -> acc + i }

val String.half: String
    get() = this.substring(0, length / 2)

// Extension property cho Android View
val View.isVisible: Boolean
    get() = visibility == View.VISIBLE

// Kiểm tra null qua receiver an toàn
val String?.isNullOrBlank: Boolean
    get() = this == null || this.isBlank()

Extension property isVisible cho View là ví dụ mọi nhà phát triển Android nên biết. Thay vì view.visibility == View.VISIBLE, bạn có thể viết view.isVisible. Điều này không chỉ ngắn hơn mà còn đọc như ngôn ngữ tự nhiên: "nếu view hiển thị". Dù đơn giản, các thuộc tính này cải thiện đáng kể khả năng đọc mã.

Các câu hỏi thường gặp

Có thể khai báo extension property cho companion object không?

Không, extension properties không thể khai báo cho companion object hoặc object declaration. Cơ chế mở rộng chỉ áp dụng cho lớp, giao diện và kiểu nullable. Với object, hãy dùng hàm thông thường cấp cao nhất.

Extension property khác với inline property thế nào?

Inline property (với bổ từ inline) là cơ chế Kotlin để gọi getter/setter mà không tạo đối tượng thuộc tính. Extension property luôn được biên dịch thành phương thức tĩnh, còn inline property được biên dịch thành lời gọi không có wrapper. Chúng giải quyết vấn đề khác nhau: extension property thêm thuộc tính vào kiểu hiện có, inline tối ưu hóa lời gọi thuộc tính riêng.

Extension properties có hỗ trợ chú thích không?

, extension property có thể có chú thích, nhưng chỉ ở cấp khai báo. Không thể chú thích riêng getter hay setter của extension property — không giống thuộc tính lớp thông thường. Ví dụ: @JvmName("getIsValid") val String.isValid get() = true.

Có thể dùng extension property với companion object của lớp không?

Không, extension properties không thể khai báo với companion object làm receiver. Đây là hạn chế ngôn ngữ — extension property chỉ hoạt động với thể hiện kiểu, còn companion object là ngữ cảnh tĩnh. Hãy dùng extension function cấp cao nhất hoặc hằng số.

Extension property có ảnh hưởng đến kích thước APK không?

Tối thiểu. Mỗi extension property thêm một phương thức getter tĩnh (và tùy chọn setter) vào bytecode đã biên dịch. So sánh, tạo lớp wrapper với cùng thuộc tính thêm cả một lớp. Extension properties là cách tiếp cận nhẹ hơn để mở rộng chức năng.

Tổng kết

  • Extension property — thuộc tính tính toán cho kiểu hiện có không cần kế thừa
  • Không có backing field — trạng thái không được lưu, chỉ tính qua getter
  • var với setter — khả thi, nhưng cần bộ nhớ ngoài để ghi dữ liệu
  • Cú pháp — val/var với receiver type và getter bắt buộc
  • Hiệu suất — không chi phí, biên dịch thành phương thức tĩnh
  • Ứng dụng — đặc điểm tính toán: độ dài, trạng thái, kích thước, kiểm tra
  • Hạn chế — không phù hợp lưu trạng thái, lưu đệm không quản lý bộ nhớ

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