extension property — กลไกของ Kotlin ที่อนุญาตให้เพิ่มคุณสมบัติใหม่ให้กับคลาสที่มีอยู่โดยไม่ต้องสืบทอดและไม่ต้องแก้ไขซอร์สโค้ด แตกต่างจาก extension functions ตรงที่ extension properties ไม่สามารถเก็บสถานะได้ — ถูกประกาศด้วย getter เท่านั้น และเลือกได้ว่าจะมี setter หรือไม่ เนื่องจากไม่มี backing field ตาม Kotlin Documentation, 2025 extension properties ถูกคอมไพล์เป็น เมธอด getter และ setter แบบสแตติก โดยมี receiver เป็นพารามิเตอร์แรก
ประเด็นสำคัญ
Extension property คือโครงสร้างทางไวยากรณ์ใน Kotlin ที่เพิ่มคุณสมบัติให้กับชนิดที่มีอยู่โดยไม่ต้องเปลี่ยนการประกาศของมัน คุณสมบัติถูกประกาศด้วย receiver type และต้องมี getter ความแตกต่างหลัก จากคุณสมบัติทั่วไปคือการไม่มี backing field: extension property ไม่สามารถเก็บข้อมูลได้ มีเพียงคำนวณตามออบเจกต์ receiver
ตาม แบบสำรวจ Kotlin Foundation (2024) extension properties ได้รับความนิยมน้อยกว่า extension functions — ประมาณ 45% ของนักพัฒนาใช้งานเป็นประจำ สาเหตุมาจากข้อจำกัดเรื่องการไม่มีสถานะ ซึ่งจำกัดขอบเขตการใช้งาน อย่างไรก็ตาม สำหรับคุณสมบัติที่คำนวณได้ซึ่งเชื่อมโยงกับชนิดอย่างมีเหตุผล extension properties เป็นตัวเลือกที่กระชับที่สุด
Extension properties ถูกคอมไพล์เป็นคู่ของเมธอด getter และ setter แบบสแตติก ในระดับไบต์โค้ด ไม่มีความแตกต่างระหว่างการเรียก extension property และการเรียก extension function — ทั้งคู่กลายเป็นเมธอดสแตติกที่มีพารามิเตอร์ receiver ตาม JetBrains (Kotlin Docs, 2025) ไม่มีโอเวอร์เฮดใดๆ
ใช้ extension properties สำหรับค่าที่คำนวณสั้นๆ ที่ควรดูเหมือนคุณสมบัติมากกว่าการเรียกเมธอด — ซึ่งช่วยปรับปรุงความสามารถในการอ่านโค้ดและปฏิบัติตามหลักการเข้าถึงอย่างสม่ำเสมอ
ในการประกาศ extension property ให้ใช้ไวยากรณ์คล้ายคุณสมบัติทั่วไป แต่มีคำนำหน้าเป็น receiver type val ประกาศ extension property แบบอ่านอย่างเดียวที่มี getter จำเป็น var ประกาศแบบ mutable ที่มี getter และ setter เป็นทางเลือก
// Extension property แบบอ่านอย่างเดียว
val String.isEmail: Boolean
get() = this.contains("@") && this.contains(".")
// เรียก
val valid = "test@test.com".isEmail
หมายเหตุ: extension property ถูกเรียกโดยวงเล็บ — str.isEmail ไม่ใช่ str.isEmail() นี่คือความแตกต่างหลักระหว่าง extension property และ extension function: คุณสมบัติดูเหมือนฟิลด์ แม้ว่าจริงๆ แล้วจะคำนวณผ่าน getter
Extension properties สามารถเป็น generic ได้ — receiver สามารถใช้พารามิเตอร์ชนิดทั่วไป ซึ่งช่วยให้สร้างคุณสมบัติสากลที่ทำงานกับชนิดคอลเลกชันใดๆ
val List<T>.secondOrNull: T?
get() = if (size >= 2) this[1] else null
val items = listOf("a", "b", "c")
val second = items.secondOrNull // "b"
คุณสมบัติ secondOrNull ทำงานกับชนิด T ใดๆ โดยคืนค่าองค์ประกอบที่สองของรายการหรือ null หากมีน้อยกว่าสององค์ประกอบ นี่คือตัวอย่างทั่วไปที่ extension property เหมาะสมกว่าฟังก์ชัน — การเข้าถึงดูเหมือนการอ่านฟิลด์
Extension property ไม่สามารถมี backing field ได้เพราะไม่ได้ถูกเพิ่มเข้าไปในเมทาดาทาของคลาส — มันมีอยู่เพียงเป็นคู่ของฟังก์ชัน getter/setter แบบสแตติก Backing field (คีย์เวิร์ด field ใน Kotlin) คือฟิลด์ภายในของคลาสที่เก็บค่าคุณสมบัติ Extension property ไม่สามารถเข้าถึงโครงสร้างภายในของคลาส
// ❌ ข้อผิดพลาด: extension property ไม่สามารถมี backing field
var String.cachedValue: String
get() = "computed"
set(value) {
field = value // ไม่สามารถเข้าถึงฟิลด์!
}
// ✅ ถูกต้อง: ใช้พื้นที่จัดเก็บภายนอก
val cache = MutableMap<String, String>()
var String.cachedValue: String
get() = cache[this] ?: ""
set(value) { cache[this] = value }
Map ภายนอก ในตัวอย่างแก้ปัญหาการจัดเก็บแต่สร้างปัญหาอื่น — หน่วยความจำรั่วไหล ค่าที่ได้รับผ่าน extension property อยู่ใน Map ตลอดไปหากไม่ถูกล้าง ข้อจำกัดนี้ทำให้ extension properties ไม่เหมาะสมสำหรับการแคชหรือเก็บข้อมูลชั่วคราว
สำหรับการแคช แนะนำให้ใช้ WeakHashMap หรือกลไกที่มีการล้างอัตโนมัติ JetBrains แนะนำให้หลีกเลี่ยงการใช้ var extension properties กับพื้นที่จัดเก็บภายนอกในโค้ดการผลิตโดยไม่มีการจัดการวงจรชีวิตอย่างระมัดระวัง
การเลือกระหว่าง extension property และ extension function ขึ้นอยู่กับความหมาย: คุณสมบัติอธิบายลักษณะของออบเจกต์ ในขณะที่ฟังก์ชันอธิบายการกระทำ หลักการเข้าถึงอย่างสม่ำเสมอ กล่าวว่า: ไคลเอนต์ไม่ควรรู้ว่าค่าถูกคำนวณหรือถูกเก็บ หากค่าสามารถแสดงเป็นลักษณะ (ความยาว ขนาด สถานะ) — ให้ใช้คุณสมบัติ
| เกณฑ์ | Extension property | Extension function |
|---|---|---|
| การเรียก | ไม่มีวงเล็บ: obj.property | มีวงเล็บ: obj.function() |
| ความหมาย | ลักษณะเฉพาะ, คุณลักษณะ | การกระทำ, การดำเนินการ |
| Backing field | ไม่รองรับ | ไม่เกี่ยวข้อง |
| พารามิเตอร์ | เฉพาะ getter/setter | พารามิเตอร์ใดๆ |
| ประสิทธิภาพ | เหมือนกัน (เมธอดสแตติก) | เหมือนกัน (เมธอดสแตติก) |
| ตัวอย่าง | text.length | text.isEmail() |
กฎง่ายๆ: หากการดำเนินการรับพารามิเตอร์ — ใช้ extension function หากเป็นค่าที่คำนวณง่ายๆ ไม่มีพารามิเตอร์ — ใช้ extension property ตาม Android Architecture Guide (Google, 2025) ควรให้ความสำคัญกับ extension properties สำหรับการเข้าถึงข้อมูลและ extension functions สำหรับการดำเนินการที่มีผลข้างเคียง
Extension property ที่มีคีย์เวิร์ด var รองรับ setter แต่ไม่มีความสามารถในการเก็บค่า — setter มักจะดำเนินการผลข้างเคียงหรือบันทึกข้อมูลในพื้นที่จัดเก็บภายนอก ไวยากรณ์ คล้ายกับคุณสมบัติคลาสแบบ mutable
// Mutable extension property พร้อม 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!
คุณสมบัติ lastChar เป็นตัวอย่างคลาสสิกจากเอกสาร Kotlin Getter คืนค่าอักขระสุดท้ายของ StringBuilder setter แทนที่ด้วยค่าใหม่ หมายเหตุ: สถานะถูกเก็บใน StringBuilder เอง (ผ่าน setCharAt) ไม่ใช่ในฟิลด์แยกต่างหาก — นี่คือการใช้ extension property ที่ถูกต้อง
ในโปรเจกต์จริง extension properties มักถูกใช้เพื่อลดความซับซ้อนในการเข้าถึงข้อมูลคอลเลกชัน คำนวณขนาดหรือสถานะขององค์ประกอบ UI และสร้าง API ที่สะดวกเหนือคลาสที่มีอยู่ ไลบรารีมาตรฐานของ Kotlin ใช้กลไกนี้อย่างแข็งขัน: size, indices, lastIndex สำหรับคอลเลกชันคือ extension properties
// Extension properties สำหรับคอลเลกชัน
val List<Int>.sumFast: Int
get() = fold(0) { acc, i -> acc + i }
val String.half: String
get() = this.substring(0, length / 2)
// Extension property สำหรับ Android View
val View.isVisible: Boolean
get() = visibility == View.VISIBLE
// ตรวจสอบ null ผ่าน receiver ที่ปลอดภัย
val String?.isNullOrBlank: Boolean
get() = this == null || this.isBlank()
Extension property isVisible สำหรับ View เป็นตัวอย่างที่นักพัฒนา Android ทุกคนควรรู้ แทนที่จะใช้ view.visibility == View.VISIBLE คุณสามารถเขียน view.isVisible ซึ่งไม่เพียงสั้นกว่า แต่ยังอ่านได้เหมือนภาษาธรรมชาติ: “หาก view มองเห็นได้” แม้จะเรียบง่าย แต่คุณสมบัติเหล่านี้ช่วยปรับปรุงความสามารถในการอ่านโค้ดอย่างมีนัยสำคัญ
คำถามที่พบบ่อย
ไม่ ไม่สามารถประกาศ extension properties สำหรับ companion object หรือ object declaration ได้ กลไก extension ใช้ได้กับคลาส อินเทอร์เฟซ และชนิด nullable เท่านั้น สำหรับ object ให้ใช้ฟังก์ชันระดับบนสุดปกติ
Inline property (กับตัวปรับแต่ง inline) เป็นกลไกของ Kotlin สำหรับเรียก getter/setter โดยไม่สร้างออบเจกต์คุณสมบัติ Extension property ถูกคอมไพล์เป็นเมธอดสแตติกเสมอ ในขณะที่ inline property ถูกคอมไพล์เป็นการเรียกโดยไม่มีตัวห่อหุ้ม ทั้งสองแก้ปัญหาที่แตกต่างกัน: extension property เพิ่มคุณสมบัติให้กับชนิดที่มีอยู่ inline ปรับการเรียกคุณสมบัติของตัวเองให้เหมาะสม
ใช่ extension property สามารถมีคำอธิบายประกอบได้ แต่เฉพาะในระดับการประกาศเท่านั้น ไม่สามารถใส่คำอธิบายประกอบแยกสำหรับ getter หรือ setter ของ extension property — ต่างจากคุณสมบัติคลาสทั่วไป ตัวอย่าง: @JvmName("getIsValid") val String.isValid get() = true.
ไม่ ไม่สามารถประกาศ extension properties กับ companion object เป็น receiver ได้ นี่คือข้อจำกัดของภาษา — extension property ทำงานกับอินสแตนซ์ของชนิดเท่านั้น ในขณะที่ companion object เป็นบริบทแบบสแตติก ใช้ฟังก์ชัน extension ระดับบนสุดหรือค่าคงที่
น้อยที่สุด แต่ละ extension property เพิ่มเมธอด getter แบบสแตติกหนึ่งเมธอด (และเลือกได้ว่าจะมี setter) ลงในไบต์โค้ดที่คอมไพล์แล้ว สำหรับการเปรียบเทียบ การสร้างคลาสตัวห่อหุ้มที่มีคุณสมบัติเดียวกันเพิ่มทั้งคลาส Extension properties เป็นแนวทางที่เบากว่าในการขยายฟังก์ชันการทำงาน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม