inline function — กลไกของ Kotlin ที่เนื้อหาของฟังก์ชันถูกแทนที่โดยตรงในทุกตำแหน่งที่เรียกใช้ในเวลาคอมไพล์ สิ่งนี้กำจัดโอเวอร์เฮดจากการสร้างคลาสนิรนามและอ็อบเจกต์สำหรับพารามิเตอร์แลมบ์ดา ตาม Kotlin Documentation, 2025 คีย์เวิร์ด inline มีประสิทธิภาพโดยเฉพาะอย่างยิ่งสำหรับฟังก์ชันลำดับสูง ซึ่ง แลมบ์ดาแต่ละตัวที่ไม่ได้อินไลน์จะสร้างอ็อบเจกต์ FunctionN แยกต่างหาก สร้างภาระให้กับตัวเก็บขยะ
ประเด็นสำคัญ
ฟังก์ชันอินไลน์คือฟังก์ชันที่ทำเครื่องหมายด้วยคีย์เวิร์ด inline คอมไพเลอร์ Kotlin จะไม่สร้างไบต์โค้ดแยกต่างหากพร้อมการเรียกใช้สำหรับมัน — แต่จะคัดลอกเนื้อหาของฟังก์ชันไปยังแต่ละตำแหน่งที่เรียกใช้โดยตรง เป้าหมายหลัก คือการเพิ่มประสิทธิภาพฟังก์ชันลำดับสูงที่รับนิพจน์แลมบ์ดา เนื่องจากแลมบ์ดาแต่ละตัวในสถานการณ์ปกติจะสร้างอ็อบเจกต์คลาส Function นิรนาม
ตาม JetBrains Tech Blog (2024) การใช้ฟังก์ชันอินไลน์ใน Kotlin สามารถลดจำนวนอ็อบเจกต์ที่สร้างขึ้นได้ 40–60% ในฟังก์ชันที่ใช้แลมบ์ดาอย่างเข้มข้น ในลูปและการทำงานที่มีโหลดสูง (การเรียงลำดับ การกรองคอลเลกชัน) สิ่งนี้ให้ประโยชน์ด้านประสิทธิภาพที่วัดได้
หากไม่มี inline แลมบ์ดาแต่ละตัวจะถูกคอมไพล์เป็นคลาสนิรนาม (หรืออินสแตนซ์ของอินเทอร์เฟซฟังก์ชันที่สังเคราะห์ขึ้น) สำหรับแลมบ์ดาที่จับตัวแปร จะสร้างอ็อบเจกต์ wrapper เพิ่มเติม ฟังก์ชันอินไลน์กำจัดอ็อบเจกต์เหล่านี้ทั้งหมดในเวลาคอมไพล์ แทนที่ด้วยโค้ดโดยตรงที่เข้าถึงตัวแปรท้องถิ่นโดยไม่มี wrapper
ใช้ inline เฉพาะกับฟังก์ชันที่มีพารามิเตอร์แลมบ์ดา — คอมไพเลอร์ Kotlin จะเตือนเองหาก inline ไม่ให้ประโยชน์
เพียงเพิ่มคีย์เวิร์ด inline ก่อนการประกาศฟังก์ชัน คอมไพเลอร์จะแทนที่เนื้อหาของฟังก์ชันในตำแหน่งที่เรียกใช้โดยอัตโนมัติ ฟังก์ชันเองยังคงมีอยู่ในไบต์โค้ดสำหรับกรณีที่ไม่ได้ถูกเรียกโดยตรง (เช่น จากโค้ด Java)
inline fun Int.repeatAction(action: (Int) -> Unit) {
for (i in 0 until this) {
action(i)
}
}
// การเรียก — โค้ดแลมบ์ดาถูกอินไลน์ในเนื้อหาฟังก์ชัน
5.repeatAction { index ->
println("Index: $index")
}
หลังจากคอมไพล์ โค้ดด้านบนจะเทียบเท่ากับ:
// สิ่งที่เกิดขึ้นหลังอินไลน์ (โดยแผนภาพ):
val $this = 5
for (i in 0 until $this) {
println("Index: $i")
}
ไม่มีอ็อบเจกต์ถูกสร้างขึ้นสำหรับแลมบ์ดา — โค้ด action ทำงานโดยตรง นี่คือ แก่นแท้ของการเพิ่มประสิทธิภาพ: แทนที่จะเรียก Function.invoke() — การแทรกโค้ดโดยตรงพร้อมเนื้อหาของแลมบ์ดา
เพื่อตรวจสอบอินไลน์ ให้เปิด Tools > Kotlin > Show Kotlin Bytecode ใน IntelliJ IDEA แล้วคลิก Decompile คุณจะเห็นว่าแทนที่จะเรียก repeatAction ด้วยแลมบ์ดา จะมีการแทรกเนื้อหาฟังก์ชันโดยตรงพร้อมลูป for
แลมบ์ดาแต่ละตัวใน Kotlin จะถูกคอมไพล์เป็นหนึ่งในสามรูปแบบ รูปแบบแรก — ถ้าแลมบ์ดาไม่จับตัวแปร มันจะกลายเป็นเมธอดสแตติกของคลาสที่ประกาศไว้ รูปแบบที่สอง — ถ้ามันจับตัวแปรหนึ่งตัว จะสร้างคลาสนิรนาม รูปแบบที่สาม — ถ้ามันจับหลายตัวแปร จะสร้างคลาสนิรนามพร้อมฟิลด์สำหรับตัวแปรที่จับแต่ละตัว
| ประเภทแลมบ์ดา | ไม่มี inline | มี inline |
|---|---|---|
| ไม่มีการจับ | เมธอดสแตติกหนึ่งตัว (ใช้ซ้ำ) | อินไลน์เต็มรูปแบบ ไม่มีการเรียก |
| จับ 1 ตัวแปร | คลาสนิรนาม (หนึ่งอ็อบเจกต์) | อินไลน์เต็มรูปแบบ ไม่มีอ็อบเจกต์ |
| จับ N ตัวแปร | คลาสนิรนามพร้อม N ฟิลด์ | อินไลน์เต็มรูปแบบ ไม่มีอ็อบเจกต์ |
| เรียกซ้ำ | เรียกใช้ปกติ | inline ถูกห้าม |
ตาม Android Performance Patterns (Google, 2024) ในแอปพลิเคชันที่ใช้คอลเลกชันอย่างเข้มข้น (การกรอง การเรียงลำดับ การจัดกลุ่ม) ฟังก์ชันอินไลน์ลดการจัดสรรลง 25–35% ผลกระทบสังเกตได้ชัดเจนเป็นพิเศษใน Jetpack Compose ซึ่งการเปลี่ยนแปลงสถานะแต่ละครั้งจะกระตุ้นการประกอบใหม่ด้วยแลมบ์ดาจำนวนมาก
แลมบ์ดาในฟังก์ชันปกติไม่สามารถ return จากฟังก์ชันภายนอกได้ — มีเพียง return ท้องถิ่นจากตัวแลมบ์ดาเอง (ผ่าน return@label) ในฟังก์ชันอินไลน์ แลมบ์ดาจะถูกอินไลน์เข้าไปในเนื้อหาของฟังก์ชันที่เรียก ดังนั้น nonlocal return จึงเป็นไปได้: return ภายในแลมบ์ดาจะสิ้นสุดฟังก์ชันภายนอก
inline fun findFirst(
items: List<Int>,
predicate: (Int) -> Boolean
): Int {
for (item in items) {
if (predicate(item)) {
return item
}
}
return -1
}
fun processNumbers() {
val numbers = listOf(1, 2, 3)
val firstEven = findFirst(numbers) { it % 2 == 0 }
// return ในแลมบ์ดาจะคืนค่า null จาก processNumbers()
}
Nonlocal return สะดวกสำหรับการสิ้นสุดก่อนกำหนด แต่อาจนำไปสู่ข้อผิดพลาด หากแลมบ์ดาถูกใช้ในบริบทที่ไม่ใช่ท้องถิ่น (เก็บไว้ในตัวแปร) nonlocal return จะทำให้เกิด RuntimeException คอมไพเลอร์ Kotlin จะออกคำเตือนเมื่อพยายามจัดเก็บดังกล่าว
เมื่อฟังก์ชันมีพารามิเตอร์แลมบ์ดาหลายตัว บางครั้งจำเป็นต้องอินไลน์เพียงบางส่วนเท่านั้น สำหรับสิ่งนี้ใช้ noinline — มันป้องกันการอินไลน์พารามิเตอร์แลมบ์ดาที่เฉพาะเจาะจง โดยปล่อยให้เป็นอ็อบเจกต์ Function ปกติ
ตัวปรับ crossinline แก้ปัญหาตรงกันข้าม: แลมบ์ดาถูกอินไลน์ แต่ nonlocal return ถูกห้าม สิ่งนี้จำเป็นเมื่อใช้แลมบ์ดาภายในแลมบ์ดาอื่นหรือในบริบทที่ไม่อนุญาตให้ return (เช่น ส่งต่อไปยัง Runnable)
inline fun processWithCallback(
data: String,
crossinline onSuccess: (String) -> Unit,
noinline onError: (Exception) -> Unit
) {
try {
val result = process(data)
onSuccess(result)
} catch (e: Exception) {
onError(e)
}
}
// noinline: onError สามารถเก็บในตัวแปรหรือส่งไปที่อื่นได้
val errorHandler = { e: Exception -> log(e.message) }
processWithCallback("input", { println(it) }, errorHandler)
ในตัวอย่าง onSuccess ถูกทำเครื่องหมายเป็น crossinline — มันจะถูกอินไลน์ แต่ไม่สามารถใช้ return ภายในมันได้ onError ถูกทำเครื่องหมายเป็น noinline — มันจะไม่ถูกอินไลน์ ดังนั้นจึงสามารถส่งต่อเป็นอ็อบเจกต์ เก็บไว้ในฟิลด์คลาส หรือใช้เป็น listener
ฟังก์ชันอินไลน์มีข้อจำกัด ฟังก์ชันอินไลน์แบบเรียกซ้ำ ถูกห้าม — คอมไพเลอร์จะคืนค่าข้อผิดพลาด ฟังก์ชันอินไลน์ไม่สามารถมีการมองเห็นแบบ private หรือ internal หากประกาศในโมดูลอื่น แต่これはข้อจำกัดด้านการมองเห็น ไม่เกี่ยวข้องกับกลไกอินไลน์เอง
ขนาดไบต์โค้ดเพิ่มขึ้นทุกครั้งที่มีการเรียกฟังก์ชันอินไลน์ เนื่องจากเนื้อหาถูกคัดลอก ตาม Kotlin Coding Conventions (JetBrains, 2025) แนะนำให้ใช้ inline เฉพาะกับฟังก์ชันที่มีขนาดไม่เกิน 10–15 บรรทัด สำหรับฟังก์ชันขนาดใหญ่ ประโยชน์จากการอินไลน์แลมบ์ดาอาจถูกหักล้างด้วยขนาด APK ที่เพิ่มขึ้น (สำคัญใน Android เนื่องจากขีดจำกัด 64K เมธอด)
// แนวทางที่แนะนำ
inline fun withLock(lock: Lock, action: () -> T): T {
lock.lock()
try {
return action()
} finally {
lock.unlock()
}
}
// ไม่แนะนำสำหรับฟังก์ชันขนาดใหญ่
inline fun largeComputation(...) { // ไม่ดี — เนื้อหา >50 บรรทัด
// มากกว่า 50 บรรทัด — ควรแยกเป็นฟังก์ชันปกติ
}
ฟังก์ชันอินไลน์สาธารณะในไลบรารีต้องใช้ความระมัดระวัง: หากเนื้อหาของฟังก์ชันอินไลน์เปลี่ยนแปลง ลูกค้าทั้งหมดต้องคอมไพล์ใหม่ JetBrains แนะนำให้ใช้ @PublishedApi internal สำหรับสมาชิกที่ถูกเรียกจากฟังก์ชันอินไลน์เพื่อรักษาความเข้ากันได้ภายในโมดูล
คำถามที่พบบ่อย
ได้ ฟังก์ชันส่วนขยายอินไลน์ทำงานได้โดยไม่มีข้อจำกัด ตัวอย่าง: inline fun String.transform(block: (Char) -> Char): String ส่วนขยายไม่ส่งผลต่อความสามารถในการอินไลน์ — คอมไพเลอร์จัดการมันเหมือนกับฟังก์ชันอินไลน์ปกติ
ถ้าฟังก์ชันไม่รับพารามิเตอร์แลมบ์ดา — inline ไม่ให้ประโยชน์ คอมไพเลอร์ Kotlin จะออกคำเตือน: “Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types.” นอกจากนี้ inline เป็นอันตรายสำหรับฟังก์ชันขนาดใหญ่เนื่องจากการเติบโตของไบต์โค้ด
inline — ตัวปรับฟังก์ชันที่อินไลน์เนื้อหาฟังก์ชันในตำแหน่งที่เรียกใช้ @JvmInline (value class) — กลไกสำหรับคลาส wrapper ที่ถูกแทนที่ด้วยค่าของมันในเวลาคอมไพล์ แนวคิดที่แตกต่าง: inline เพิ่มประสิทธิภาพการเรียกใช้ value class เพิ่มประสิทธิภาพการแสดงข้อมูล
ไม่ ฟังก์ชัน suspend ไม่สามารถเป็น inline ได้因为它们ถูกคอมไพล์เป็น state machine พร้อม Continuation อย่างไรก็ตาม ฟังก์ชันอินไลน์สามารถรับแลมบ์ดา suspend เป็นพารามิเตอร์ด้วย crossinline สิ่งนี้มักใช้ใน coroutines: inline fun launch(block: suspend CoroutineScope.() -> Unit)
ใช่ ฟังก์ชันอินไลน์ทำให้การดีบักซับซ้อนขึ้นเนื่องจากเนื้อหาฟังก์ชันไม่ถูกเรียก แต่ถูกอินไลน์ในตำแหน่งที่เรียกใช้ Stacktrace ยาวขึ้น จุดพักทำงานแต่สามารถแสดงตำแหน่งที่ไม่คาดคิด JetBrains แนะนำให้ดีบักโดยไม่มี inline และเปิดใช้งานเฉพาะในบิลด์ release
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม