ภาพรวม
Kotlin มีกลไกสองแบบสำหรับการเริ่มต้นค่า Property แบบล่าช้า (Lazy Initialization): lateinit และ by lazy ทั้งคู่แก้ปัญหา "ต้องการประกาศตัวแปรแต่ยังไม่มีค่าในขณะสร้าง Object" แต่มีความแตกต่างสำคัญ
lateinit - เริ่มต้นภายหลังด้วยตนเอง
เงื่อนไขการใช้งาน
- ต้องเป็น
var(mutable) - ต้องเป็น Non-null type
- ไม่รองรับ Primitive types (Int, Boolean, etc.)
- ไม่มี Custom Getter/Setter
ตัวอย่าง
class Tutorial {
lateinit var name: String
fun checkLateInit() {
// ตรวจสอบว่า initialize แล้วหรือยัง
if (::name.isInitialized) {
println("ค่า: $name")
} else {
println("ยังไม่ได้กำหนดค่า")
name = "www.tutorialspoint.com"
println("กำหนดค่าแล้ว: $name")
}
}
}
fun main() {
val obj = Tutorial()
obj.checkLateInit() // ยังไม่ได้กำหนดค่า -> กำหนดค่า -> แสดงผล
obj.checkLateInit() // แสดงค่าที่กำหนดไว้แล้ว
}
คุณสมบัติ
- คุณควบคุม เมื่อไหร่ และ อย่างไร ที่จะกำหนดค่า
- สามารถกำหนดค่าใหม่ได้หลายครั้ง (เพราะเป็น var)
- ถ้าเข้าถึงก่อน initialize → โยน
UninitializedPropertyAccessException - ตรวจสอบได้ด้วย
::property.isInitialized
by lazy - เริ่มต้นอัตโนมัติครั้งแรกที่เข้าถึง
เงื่อนไขการใช้งาน
- ต้องเป็น
val(immutable reference) - Thread-safe โดยค่าเริ่มต้น (SYNCHRONIZED mode)
- คำนวณค่าเพียงครั้งเดียว แล้ว Cache ไว้ใช้ต่อไป
ตัวอย่าง
class Demo {
val myName: String by lazy {
println("🔄 กำลังคำนวณค่า... (รันเพียงครั้งเดียว)")
"www.tutorialspoint.com"
}
}
fun main() {
val obj = Demo()
println("ครั้งที่ 1: ${obj.myName}") // พิมพ์ "กำลังคำนวณ..." แล้วค่า
println("ครั้งที่ 2: ${obj.myName}") // ใช้ค่า Cache ทันที ไม่พิมพ์ข้อความ
println("ครั้งที่ 3: ${obj.myName}") // ใช้ค่า Cache
}
โหมด Lazy (LazyThreadSafetyMode)
// Default: SYNCHRONIZED - Thread-safe, ใช้ Lock
val default: String by lazy { "default" }
// PUBLICATION - หลาย Thread อาจคำนวณซ้ำ แต่ใช้ค่าตัวแรกที่เสร็จ
val fast: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "fast" }
// NONE - ไม่ Thread-safe, เร็วที่สุด (ใช้เมื่อแน่ใจว่า Single-thread)
val unsafe: String by lazy(LazyThreadSafetyMode.NONE) { "unsafe" }
ตารางเปรียบเทียบ
| ลักษณะ | lateinit var | val by lazy |
|---|---|---|
| Mutability | Mutable (var) | Immutable reference (val) |
| การกำหนดค่า | Manual (คุณตัดสินใจ) | Automatic (ครั้งแรกที่เข้าถึง) |
| Primitive Types | ไม่รองรับ | รองรับ (ผ่าน Wrapper/Inline Class) |
| Nullability | Non-null only | Nullable OK (val name: String? by lazy { ... }) |
| Re-initialization | ได้ (assign ใหม่ได้) | ไม่ได้ (คำนวณครั้งเดียวจบ) |
| Thread Safety | ต้องจัดการเอง | Built-in (SYNCHRONIZED) |
| การตรวจสอบ Init | ::prop.isInitialized | ไม่จำเป็น (มั่นใจว่า Init แล้ว) |
แนวทางเลือกใช้
- lateinit: Dependency Injection, View Binding, Unit Test Setup, ค่าที่ตั้งค่าจากภายนอก
- by lazy: Expensive Computation, Singleton Pattern, Cache, ค่าที่คำนวณจาก Property อื่น