lateinit แลกข้อความ error ที่ดี ด้วย field ที่กลายเป็น public
lateinit ถูกเสนอเป็นทางออกของกรณีที่ค่าจริงมาทีหลัง เช่น field ที่ framework ฉีดให้
· ทางเลือกอีกทางคือประกาศเป็น nullable แล้วจ่ายด้วย ?. หรือ !! ทุกจุดที่ใช้
เรื่องที่คนเทียบกันคือความสะดวก · หน้านี้เทียบสิ่งที่ JVM เห็นแทน
ครึ่งที่ lateinit ชนะ
หัวข้อที่มีชื่อว่า “ครึ่งที่ lateinit ชนะ”ข้อความตอนแตะก่อนกำหนดค่า บอกชื่อ property ออกมาตรงๆ
class WithLateinit { lateinit var name: String }class WithNullable { var name: String? = null }| ทรง | ชนิดของ exception | ข้อความ |
|---|---|---|
lateinit แล้วอ่านก่อนกำหนดค่า | kotlin.UninitializedPropertyAccessException | lateinit property name has not been initialized |
nullable แล้วใช้ !! | java.lang.NullPointerException | null |
ฝั่งแรกบอกว่า property ชื่ออะไรและปัญหาคืออะไร · ฝั่งหลังตอบว่า null
ซึ่งเป็นข้อความที่ทุก !! ในโปรแกรมพูดเหมือนกันหมด
ส่วน ?. ไม่โยนอะไรเลย มันคืน null แล้วเดินต่อ
· นั่นคือคนละการตัดสินใจ ไม่ใช่ทางแก้ที่ดีกว่า
ครึ่งที่ lateinit จ่าย
หัวข้อที่มีชื่อว่า “ครึ่งที่ lateinit จ่าย”เปิด
javapแล้ว modifier ของ field ไม่เหมือนกัน
$ javap -p WithLateinit WithNullable| ประกาศใน Kotlin | field ที่ JVM เห็น |
|---|---|
lateinit var name: String | public java.lang.String name |
var name: String? | private java.lang.String name |
property แบบปกติได้ field เป็น private แล้วมี getter กับ setter
ครอบไว้ · ส่วน lateinit ได้ field เป็น public
ข้อความ lateinit property name has not been initialized ถูกโยนจาก getter ที่ compiler สร้าง
field ที่เป็น public แปลว่า code ที่ไม่ผ่าน getter มองเห็นค่าดิบได้ตรงๆ ทั้ง Java ที่เรียกข้ามภาษา และ library ที่เดิน field ด้วย reflection
สิ่งที่ได้จากตรงนั้นคือ null เปล่าๆ ไม่ใช่ exception ที่บอกชื่อ property
· ด่านที่หลบได้ ไม่ใช่ด่าน และ lateinit ไม่เคยสัญญาว่าจะกันตรงนั้น
isInitialized แคบกว่าที่คนคาด
หัวข้อที่มีชื่อว่า “isInitialized แคบกว่าที่คนคาด”มันไม่ใช่ property ธรรมดา และเรียกจากนอก class ไม่ได้
class WithLateinit { lateinit var name: String fun ready(): Boolean = ::name.isInitialized}| จังหวะ | ready() |
|---|---|
| ก่อนกำหนดค่า | false |
| หลังกำหนดค่า | true |
ย้ายบรรทัดเดียวกันออกไปนอก class แล้ว compiler ปฏิเสธ
error: backing field of 'var name: String' is not accessible at this point.isInitialized อ่าน backing field โดยตรง มันจึงติดกฎการมองเห็นของ field
· code ที่อยากรู้ว่า “พร้อมหรือยัง” จากข้างนอก ต้องให้ class เปิด method ให้เอง
เลือกอย่างไร
หัวข้อที่มีชื่อว่า “เลือกอย่างไร”คำถามที่ตัดสินไม่ใช่ความสะดวก แต่เป็นว่า “ไม่มีค่า” เป็นสถานะที่ถูกต้องหรือไม่
- ถ้า
nullเป็นสถานะที่ถูกต้องของโดเมน ให้ประกาศ nullable · ระบบชนิดจะบังคับให้ทุกจุดที่ใช้ตอบว่าจะทำอย่างไรกับกรณีนั้น - ถ้าไม่มีค่าคือบั๊ก และค่าจะถูกใส่ก่อนใช้เสมอ
lateinitให้ข้อความที่ดีกว่า ตอนสมมติฐานนั้นผิด - ถ้าค่าคำนวณได้จากตัวเอง ทั้งสองทางผิด · ใช้
by lazyแล้วไม่มีสถานะ “ยังไม่พร้อม” ให้ใครแตะเลย
สามทรงที่ compiler ปฏิเสธก่อนจะถึงเรื่องอื่น
หัวข้อที่มีชื่อว่า “สามทรงที่ compiler ปฏิเสธก่อนจะถึงเรื่องอื่น”ข้อจำกัดพวกนี้ตัดตัวเลือกทิ้งตั้งแต่ยังไม่ได้ชั่งข้อดีข้อเสีย
| เขียนว่า | compiler ตอบ |
|---|---|
lateinit var n: Int | 'lateinit' modifier is not allowed on properties of primitive types. |
lateinit var s: String ที่มี get() เอง | 'lateinit' modifier is not allowed on properties with a custom getter or setter. |
lateinit val s: String | 'lateinit' modifier is allowed only on mutable properties. |
ข้อแรกคือเหตุผลที่ lateinit ใช้กับ Int, Long, Boolean ไม่ได้เลย
· ด่านของมันอาศัยค่า null ในช่อง ซึ่งชนิดพื้นฐานบน JVM ไม่มีที่ให้เก็บ
ทุกค่ามาจาก kotlinc 2.4.10 กับ javap ตัวจริง
· แปลด้วย -Werror -Wextra -jvm-target 25 และไม่มีหน่วยเวลาสักตัว
python3 scripts/kotlin-bytecode-facts/probe.pynpm run verify:kotlin-bytecodeprobe มีช่องควบคุมสองช่องของหัวข้อนี้ คือ isInitialized ต้องเป็นเท็จก่อนกำหนดค่า
และจริงหลังกำหนด · และทรงที่เรียกจากนอก class ต้องแปลไม่ผ่านจริง
- Kotlin — Properties · Late-initialized properties (เข้าถึง 2026-08-30)
- Kotlin — Null safety (เข้าถึง 2026-08-30)