extension function — กลไกของ Kotlin ที่อนุญาตให้เพิ่มเมธอดใหม่ให้กับคลาสที่มีอยู่โดยไม่ต้องสืบทอดและไม่ต้องแก้ไขซอร์สโค้ด ฟังก์ชันถูกประกาศด้วยคำนำหน้าในรูปแบบของตัวรับชนิดและถูกเรียกเหมือนเมธอดปกติของชนิดนั้น ตาม Kotlin Documentation, 2025 extension functions ถูกคอมไพล์เป็นเมธอดสแตติกโดยมีตัวรับเป็นพารามิเตอร์แรก ซึ่งรับประกัน overhead เป็นศูนย์ ในรันไทม์เมื่อเทียบกับการเรียกปกติ
ประเด็นสำคัญ
Extension function คือฟังก์ชันที่ประกาศภายนอกคลาสแต่ถูกเรียกเหมือนเมธอดของคลาสนั้น ฟังก์ชันรับชนิดตัวรับซึ่งเมธอดใหม่จะถูกเพิ่มเข้าไป ภายในฟังก์ชัน คีย์เวิร์ด this อ้างถึงอินสแตนซ์ของชนิดตัวรับ วิธีการนี้ช่วยให้ขยายคลาส final คลาสไลบรารีของบุคคลที่สาม และแม้แต่ชนิดดั้งเดิมได้โดยไม่ต้องสร้าง wrapper
แตกต่างจาก Java ที่การเพิ่มเมธอดให้กับคลาสที่มีอยู่ต้องใช้การสืบทอดหรือคลาสยูทิลิตีแบบสแตติก Kotlin มอบโซลูชันที่สวยงามโดยไม่มี boilerplate ตาม การศึกษาของ Kotlin Foundation (2024) extension functions อยู่ในห้าคุณสมบัติที่ถูกใช้มากที่สุดของภาษา — นักพัฒนา Kotlin 78% ใช้มันในการทำงานประจำวัน
คอมไพเลอร์ Kotlin แปลง extension function เป็นเมธอดสแตติกซึ่งอินสแตนซ์ตัวรับถูกส่งเป็นอาร์กิวเมนต์แรก ซึ่งหมายความว่า extension functions ไม่ได้แก้ไขไบต์โค้ดของคลาสเป้าหมายและไม่ทำลายการห่อหุ้ม — เฉพาะฟิลด์และเมธอดสาธารณะของตัวรับเท่านั้นที่เข้าถึงได้
ใช้ extension functions เพื่อเขียนฟังก์ชันยูทิลิตีที่สะอาดซึ่งเชื่อมโยงเชิงตรรกะกับชนิดเฉพาะแต่ไม่สามารถเพิ่มลงในซอร์สโค้ดของมันได้
ไวยากรณ์พื้นฐานของ extension function: ชื่อชนิดตัวรับ จุด ชื่อฟังก์ชัน จากนั้นพารามิเตอร์และชนิดที่ส่งคืน ภายในฟังก์ชัน ตัวรับสามารถเข้าถึงได้ผ่าน this
// Extension function for String
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// Called as regular String method
val result = "user@example.com".isEmail() // true
ในตัวอย่างนี้ isEmail() พร้อมใช้งานสำหรับสตริงทั้งหมด ภายในฟังก์ชัน this อ้างถึงสตริงที่เมธอดถูกเรียกใช้ Kotlin อนุญาตให้ละ this ในกรณีส่วนใหญ่ — คุณสามารถเขียนแค่ contains("@") แทน this.contains("@")
Extension functions สามารถรับพารามิเตอร์เพิ่มเติมและส่งคืนค่าของชนิดใดก็ได้ สิ่งนี้ทำให้พวกมันเป็นฟังก์ชันที่สมบูรณ์ ไม่ใช่แค่น้ำตาลเชิงไวยากรณ์
fun List<Int>.defaultIfEmpty(default: Int): List<Int> {
if (this.isEmpty()) return listOf(default)
return this
}
val populated = listOf(1, 2).defaultIfEmpty(0)
val empty = listOf<Int>().defaultIfEmpty(0)
พารามิเตอร์ default กำหนดค่าที่ส่งคืนสำหรับลิสต์ว่าง Extension function สามารถเป็น generic — ชนิด List
Extension functions ใน Kotlin ใช้ การจัดส่งแบบสแตติก ไม่ใช่การจัดส่งแบบเสมือน นี่คือความแตกต่างสำคัญจากเมธอดคลาสทั่วไป ว่า extension function ใดจะถูกเรียกนั้นถูกกำหนดในเวลาคอมไพล์โดยชนิดสแตติกของตัวแปร ไม่ใช่โดยชนิดจริงในรันไทม์
open class Animal
class Dog : Animal()
fun Animal.speak() = "Animal sound"
fun Dog.speak() = "Woof"
fun test() {
val animal: Animal = Dog()
println(animal.speak()) // "Animal sound" — static type Animal
val dog: Dog = Dog()
println(dog.speak()) // "Woof" — static type Dog
}
แม้ว่าตัวแปร animal จะชี้ไปยังอินสแตนซ์ของ Dog แต่ extension function สำหรับ Animal ถูกเรียกเพราะชนิดสแตติกของตัวแปรคือ Animal ถ้า speak() เป็นเมธอดเสมือนของคลาส Dog.speak() จะถูกเรียก พฤติกรรมนี้สำคัญที่ต้องพิจารณาเมื่อออกแบบ API ด้วย extension functions
Extension functions ไม่ใช่สมาชิกของคลาส — พวกมันเป็นฟังก์ชันปกติที่ต้องการการนำเข้าเพื่อใช้ในไฟล์อื่น Kotlin มีสองตัวเลือกการนำเข้า: ตามชื่อฟังก์ชันหรือพร้อมกับการเปลี่ยนชื่อ
// Import by name
import com.example.extensions.isEmail
// Import with alias (to resolve conflicts)
import com.example.extensions.isEmail as isValidEmail
// Call after import
val valid = "test@test.com".isEmail()
การเปลี่ยนชื่อ ผ่าน as มีประโยชน์สำหรับการขัดแย้งของชื่อ — ตัวอย่างเช่น ถ้าไลบรารีสองแห่งมี extension function ชื่อเดียวกันสำหรับชนิดเดียวกัน ในกรณีนี้ คุณสามารถนำเข้าหนึ่งในนั้นด้วยชื่ออื่นและเรียกใช้โดยใช้ชื่อใหม่
Extension functions สามารถประกาศได้ในระดับต่างๆ:
| ระดับ | การมองเห็น | ตัวอย่าง |
|---|---|---|
| Top-level | ทั้งโปรเจกต์หลังนำเข้า | fun String.isEmail() |
| Member extension | ภายในคลาสเจ้าของ | class A { fun B.ext() } |
| Local | ภายในฟังก์ชัน | fun test() { fun String.ext() } |
Member extension functions เป็นกรณีพิเศษที่ extension function ถูกประกาศภายในคลาสอื่น ในกรณีนี้ ทั้งตัวรับ (this ของฟังก์ชัน) และสมาชิกของคลาสภายนอกสามารถเข้าถึงได้ภายใน extension function
ถ้าคลาสมีเมธอดที่มีลายเซ็นเดียวกับ extension function สมาชิกคลาสจะถูกเรียกเสมอ Extension function ไม่เคยแทนที่เมธอดคลาส — นี่คือการตัดสินใจเชิงสถาปัตยกรรมเพื่อป้องกันการแทนที่โดยไม่ตั้งใจ
class User {
fun greet() = "Hello from class"
}
fun User.greet() = "Hello from extension"
fun main() {
val user = User()
println(user.greet()) // "Hello from class"
}
แม้ว่า extension function จะถูกกำหนดทีหลังและมีลายเซ็นเดียวกัน คอมไพเลอร์ จะเลือกเมธอดของคลาส คอมไพเลอร์ Kotlin จะแสดงคำเตือนเมื่อตรวจพบสถานการณ์นี้ วิธีเดียวที่จะเรียก extension function เมื่อมีเมธอดคลาสอยู่คือเรียกมันเป็นฟังก์ชันปกติ: greet(user)
ในการพัฒนา Android extension functions กลายเป็นเครื่องมือมาตรฐานสำหรับการทำงานกับ View Context และ fragments ไลบรารี Android KTX สร้างขึ้นบน extension functions โดยให้ wrapper ที่สะดวกเหนือ Android API
// Extension function for working with View
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
// Extension function for Context — toast
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// Extension function as DSL builder
fun String.colorFormat(color: String): String =
"$color$this\u001b[0m"
Extension function toast() ทำให้การเรียกที่ขึ้นกับ Context กระชับ: แทนที่จะเขียน Toast.makeText(context, message, length).show() ก็แค่เขียน context.toast("ข้อความ") ซึ่งช่วยลด boilerplate และทำให้โค้ดอ่านง่ายขึ้น
คำถามที่พบบ่อย
ไม่ extension functions ใช้การจัดส่งแบบสแตติก ถ้าคุณประกาศ extension function สำหรับคลาสพื้นฐานและอันเดียวกันสำหรับคลาสย่อย อันไหนจะถูกเรียกนั้นถูกกำหนดโดยชนิดสแตติกของตัวแปรในเวลาคอมไพล์ ไม่ใช่โดยชนิดจริงในรันไทม์
คุณสามารถประกาศ extension functions สำหรับชนิดตัวรับ nullable: fun String?.isNullOrEmail() ภายในฟังก์ชันดังกล่าว this อาจเป็น null ดังนั้นคุณต้องใช้ตัวดำเนินการเรียกที่ปลอดภัยหรือการตรวจสอบ null อย่างชัดแจ้ง
ไม่ extension functions ถูกคอมไพล์เป็นเมธอดสแตติก ในระดับไบต์โค้ด การเรียก extension function เหมือนกับการเรียกเมธอดสแตติกโดยมีตัวรับเป็นพารามิเตอร์แรก ไม่มีการสะท้อนหรือการจัดส่งแบบไดนามิกใดๆ
ได้ extension properties ทำงานคล้ายกับ extension functions แต่ไม่สามารถเก็บสถานะได้ — แค่ getter และ setter ตัวอย่าง: val List
ใน Java คุณเขียน StringUtils.isEmail(str) ในขณะที่ Kotlin คุณเขียน str.isEmail() ความแตกต่างไม่ใช่แค่ไวยากรณ์: extension functions รองรับการเติมข้อความอัตโนมัติใน IDE ปรับปรุงความสามารถในการอ่านของลูกโซ่การเรียก และอนุญาตให้ IDE แนะนำฟังก์ชันที่เกี่ยวข้องสำหรับชนิดเฉพาะ Java ไม่มีความสามารถเหล่านี้
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม