lateinit / lazy: สาระสำคัญของการเริ่มต้นแบบเลื่อนเวลาและกลไกใน Kotlin

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-23 เวลาอ่าน: 8 นาที

การเริ่มต้นแบบเลื่อนเวลา (lazy initialization) เป็นกลไกใน Kotlin ซึ่งคุณสมบัติของอ็อบเจกต์ถูกเริ่มต้นไม่ใช่ในขณะที่สร้าง แต่เมื่อมีการเข้าถึงครั้งแรก ตามข้อมูลจาก JetBrains, 2024, lateinit และ lazy เป็นเครื่องมือในตัวสองอย่างสำหรับใช้กลยุทธ์นี้ ทั้งสองแก้ปัญหาการเริ่มต้นแบบเลื่อนเวลา แต่แตกต่างกันโดยพื้นฐานในกลไกการทำงานและขอบเขตการใช้งาน

ประเด็นสำคัญ

  • lateinit — ตัวปรับแต่งสำหรับคุณสมบัติ var อนุญาตให้เริ่มต้นหลังจากการสร้างอ็อบเจกต์
  • lazy — ตัวแทนสำหรับคุณสมบัติ val เริ่มต้นค่าเมื่อเข้าถึงครั้งแรก
  • lateinit ต้องการ var และไม่รองรับชนิดพื้นฐาน JVM
  • lazy ปลอดภัยต่อเธรดโดยค่าเริ่มต้นและแคชผลลัพธ์ที่คำนวณแล้ว
  • lateinit ขว้าง UninitializedPropertyAccessException เมื่อเข้าถึงก่อนกำหนด

การเริ่มต้นแบบเลื่อนเวลาใน Kotlin คืออะไร?

การเริ่มต้นแบบเลื่อนเวลา เป็นรูปแบบที่คุณสมบัติของคลาสได้รับค่าไม่ใช่ในขณะที่สร้างอ็อบเจกต์ แต่ในภายหลังเมื่อจำเป็น ใน Kotlin รูปแบบนี้ถูกนำไปใช้ด้วยสองวิธีที่แตกต่างกันโดยพื้นฐาน: ตัวปรับแต่ง lateinit และตัวแทน lazy

ทั้งสองกลไกแก้ปัญหาทั่วไป — คุณสมบัติต้องมีอยู่ในคลาส แต่ค่าของมันไม่เป็นที่รู้จักในขณะที่สร้างอ็อบเจกต์ หรือการคำนวณมันใช้ทรัพยากรมากเกินไปที่จะทำโดยไม่จำเป็น ตามข้อมูลจาก Google I/O 2023 คุณสมบัติมากถึง 40% ในแอปพลิเคชัน Android ทั่วไปสามารถปรับให้เหมาะสมผ่านการเริ่มต้นแบบเลื่อนเวลา ซึ่งลดเวลาเริ่มต้นลง 15–25%

การเลือกระหว่าง lateinit และ lazy ถูกกำหนดโดยสามปัจจัย: ความเปลี่ยนแปลงได้ของคุณสมบัติ (var หรือ val) วงจรชีวิตของมัน (การกำหนดค่าเดียวหรือหลายครั้ง) และข้อกำหนดด้านความปลอดภัยของเธรด (การเข้าถึงแบบเธรดเดียวหรือหลายเธรด)

เมื่อใดที่ใช้การเริ่มต้นแบบเลื่อนเวลา

สถานการณ์แรกและพบบ่อยที่สุดคือ การฉีดพึ่งพา เฟรมเวิร์ก (Dagger, Hilt, Koin) ฉีดพึ่งพาหลังจากการสร้างอ็อบเจกต์ ดังนั้นคุณสมบัติจึงไม่สามารถเริ่มต้นในคอนสตรักเตอร์ได้ หากไม่มี lateinit พึ่งพาทั้งหมดจะต้องถูกประกาศเป็น nullable และตรวจสอบทุกครั้งที่ใช้งาน

สถานการณ์ที่สองคือ ทรัพยากรหนัก: ฐานข้อมูล, ไคลเอนต์เครือข่าย, ตัวจัดการไฟล์ การสร้างมันต้องใช้เวลาและหน่วยความจำ ดังนั้นควรเริ่มต้นเมื่อใช้งานจริงเท่านั้น lazy เหมาะสำหรับกรณีเหล่านี้ รับประกันการสร้างครั้งเดียว

สถานการณ์ที่สามคือ คอมโพเนนต์ Android (Activity, Fragment, ViewModel) ซึ่งวงจรชีวิตถูกจัดการโดยระบบปฏิบัติการ คุณสมบัติที่ขึ้นอยู่กับ onCreate, onViewCreated หรือบล็อก init ของ ViewModel ไม่สามารถเริ่มต้นในคอนสตรักเตอร์ได้

lateinit: กลไกและข้อจำกัด

lateinit เป็นตัวปรับแต่งสำหรับคุณสมบัติ var ที่อนุญาตให้คอมไพเลอร์ Kotlin เลื่อนการเริ่มต้น คอมไพเลอร์ไม่ต้องการการกำหนดค่าในคอนสตรักเตอร์ แต่สร้างการตรวจสอบขณะรันไทม์ทุกครั้งที่มีการเข้าถึง: หากคุณสมบัติไม่ได้ถูกเริ่มต้น มันจะขว้าง UninitializedPropertyAccessException

kotlin
class MainActivity {
    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}

ข้อจำกัดของ lateinit: คุณสมบัติต้องถูกประกาศเป็น var (ไม่ใช่ val), ไม่เป็น nullable, และไม่ใช่ชนิดพื้นฐาน (Int, Double, Boolean ฯลฯ) สาเหตุคือชนิดพื้นฐานถูกคอมไพล์เป็นพื้นฐาน JVM ซึ่งไม่มีสถานะ “ไม่ได้เริ่มต้น” สำหรับคุณสมบัติที่ nullable การเริ่มต้นแบบเลื่อนเวลาไม่จำเป็น: null หมายถึงการไม่มีค่าอยู่แล้ว

ในการตรวจสอบสถานะของคุณสมบัติ lateinit ให้ใช้การอ้างอิงในตัวผ่านโอเปอเรเตอร์ ::: ::propertyName.isInitialized นี่เป็นวิธีที่ปลอดภัยเพียงวิธีเดียวในการตรวจสอบว่าคุณสมบัติถูกเริ่มต้นแล้วหรือไม่โดยไม่เสี่ยงต่อข้อยกเว้น การตรวจสอบสามารถใช้ได้จากคลาสเดียวกันหรือคลาสภายในเท่านั้น ไม่ใช่จากโค้ดภายนอก

kotlin
class LoginFragment {
    lateinit var binding: FragmentLoginBinding

    fun isReady(): Boolean {
        return ::binding.isInitialized
    }
}

ประสิทธิภาพของ lateinit

lateinit ไม่เพิ่มโอเวอร์เฮดหลังจากการเริ่มต้น: เมื่อกำหนดค่าแล้ว การเข้าถึงคุณสมบัติจะเหมือนกับการเข้าถึงฟิลด์โดยตรง ค่าใช้จ่ายเดียวคือ การตรวจสอบการเริ่มต้นในการอ่านทุกครั้งก่อนการกำหนดค่า หลังจากการเริ่มต้น คอมไพเลอร์ JIT จะปรับการตรวจสอบให้เหมาะสม

หมายเหตุสำคัญ: คุณสมบัติ lateinit ไม่สามารถใช้ใน คลาสอินไลน์ และไม่รองรับสำหรับคุณสมบัติที่มี getter/setter ที่กำหนดเอง หากคุณสมบัติต้องการการเข้าถึงแบบคำนวณ ให้ใช้ lazy แทน lateinit

lazy: กลไกและข้อดี

lazy เป็นตัวแทนคุณสมบัติที่สร้างในไลบรารีมาตรฐานของ Kotlin มันคำนวณค่าเมื่อเข้าถึงคุณสมบัติครั้งแรกและแคชผลลัพธ์สำหรับการเรียกทั้งหมดที่ตามมา ต่างจาก lateinit, lazy ทำงานกับ val เท่านั้น ทำให้คุณสมบัติไม่สามารถเปลี่ยนแปลงได้หลังจากการเริ่มต้น

kotlin
class UserRepository {
    private val database: Database by lazy {
        Database.create("users.db")
    }

    fun getUser(id: String): User {
        return database.query("SELECT * FROM users WHERE id = ?", id)
    }
}

lazy รับพารามิเตอร์เสริม LazyThreadSafetyMode ที่ควบคุมกลไกความปลอดภัยของเธรด ค่าเริ่มต้นคือ SYNCHRONIZED — การตรวจสอบซ้อนกับการล็อก รับประกันการเริ่มต้นครั้งเดียวแม้ภายใต้การเข้าถึงพร้อมกันจากหลายเธรด

โหมดความปลอดภัยของเธรดของ lazy

โหมด PUBLICATION อนุญาตให้เริ่มต้นแบบขนาน: หลายเธรดสามารถดำเนินการบล็อกเริ่มต้นพร้อมกันได้ แต่ผลลัพธ์จะถูกยอมรับจากเธรดที่เสร็จก่อนเท่านั้น ซึ่งเร็วกว่า SYNCHRONIZED เมื่อมีการแข่งขันสูง แต่เพิ่มการใช้ทรัพยากร

โหมด NONE ปิดการซิงโครไนซ์โดยสมบูรณ์ ใช้สำหรับคุณสมบัติที่รับประกันการเข้าถึงจากเธรดเดียวเท่านั้น ในโหมดนี้ lazy ทำงานด้วยโอเวอร์เฮดน้อยที่สุด — เกือบเหมือนการกำหนดค่าโดยตรง

kotlin
val heavyConfig: Config by lazy(LazyThreadSafetyMode.NONE) {
    Config.loadFromFile("config.json")
}

เมื่อใดที่ lazy เหมาะสมกว่า

lazy เป็นตัวเลือกที่ถูกต้องสำหรับพึ่งพาที่ เริ่มต้นครั้งเดียว: ที่เก็บข้อมูล, ไคลเอนต์เครือข่าย, แคช, ฐานข้อมูล ความหมายของ val ป้องกันการเขียนทับโดยไม่ตั้งใจ และความปลอดภัยของเธรดโดยค่าเริ่มต้นทำให้โค้ดปลอดภัยในสภาพแวดล้อมแบบหลายเธรด lazy ยังทำงานถูกต้องกับ ชนิดพื้นฐาน ซึ่งเป็นไปไม่ได้กับ lateinit

ใน Android lazy มักใช้สำหรับเริ่มต้น พึ่งพาของ ViewModel ผ่าน by viewModels() หรือสำหรับสร้างไคลเอนต์ Retrofit อย่างไรก็ตาม ระวัง: หากบล็อก lazy จับการอ้างอิงไปยัง Activity หรือ Fragment มันอาจทำให้ หน่วยความจำรั่ว เนื่องจากตัวแทนเก็บการปิดไว้ตลอดอายุของคุณสมบัติ

lateinit vs lazy: การเปรียบเทียบแนวทาง

การเลือกระหว่าง lateinit และ lazy ไม่ใช่เรื่องของความชอบ แต่เป็นการตัดสินใจทางสถาปัตยกรรมที่กำหนดโดยธรรมชาติของคุณสมบัติ แต่ละกลไกแก้ปัญหาของตัวเอง และพื้นที่การใช้งานของมันทับซ้อนกันเพียงบางส่วน

เกณฑ์lateinitlazy
ชนิดคุณสมบัติเฉพาะ varเฉพาะ val
Nullableไม่อนุญาตอนุญาต
ชนิดพื้นฐานไม่อนุญาตอนุญาต
ความปลอดภัยของเธรดไม่รับประกันSYNCHRONIZED โดยค่าเริ่มต้น
การตรวจสอบสถานะ::x.isInitializedไม่จำเป็น
ข้อยกเว้นเมื่อผิดพลาดUninitializedPropertyAccessExceptionข้อผิดพลาดในบล็อก init
การแคชใช้ไม่ได้คำนวณครั้งเดียว
Android BindingView Binding, Data Bindingไม่ใช้
เฟรมเวิร์ก DIDagger, Hilt, Koinฉีดด้วยตนเอง

ใช้ lateinit เมื่อคุณสมบัติต้องเปลี่ยนแปลงหลังจากการเริ่มต้นหรือการสร้างถูกจัดการโดยโค้ดภายนอก ตัวอย่างทั่วไปคือ View Binding ใน Android Activity: binding ถูกสร้างใน onCreate แต่ยังคงเป็น var เพราะเฟรมเวิร์กไม่รองรับ val สำหรับสถานการณ์นี้

ใช้ lazy เมื่อคุณสมบัติถูกเริ่มต้นครั้งเดียว การคำนวณมีราคาแพง และค่าไม่เปลี่ยนแปลงตลอดอายุของอ็อบเจกต์ ตัวอย่างคลาสสิกคือการสร้าง ไคลเอนต์ Retrofit หรือฐานข้อมูล Room แบบขี้เกียจเมื่อเข้าถึงที่เก็บข้อมูลครั้งแรก

การรวม lateinit และ lazy

ทั้งสองกลไกสามารถใช้พร้อมกันภายในคลาสเดียวกัน ตัวอย่างเช่น lateinit สำหรับ View Binding และ lazy สำหรับที่เก็บข้อมูล นี่เป็นปฏิบัติปกติที่สะท้อนความต้องการที่แตกต่างกันสำหรับคุณสมบัติที่แตกต่างกัน สิ่งสำคัญคือไม่สับสนความหมาย: อย่าใช้ lateinit ในที่ที่ต้องการ val และอย่าใช้ lazy สำหรับคุณสมบัติที่ต้องกำหนดค่าใหม่

ข้อผิดพลาดทั่วไปเมื่อใช้ lateinit และ lazy

ข้อผิดพลาดที่พบบ่อยที่สุดกับ lateinit คือการเข้าถึงคุณสมบัติก่อนที่จะถูกเริ่มต้น นำไปสู่ UninitializedPropertyAccessException ซึ่งไม่ถูกจับในขณะคอมไพล์เพราะ Kotlin เชื่อถือว่านักพัฒนาจะรับประกันลำดับการเริ่มต้นที่ถูกต้อง วิธีแก้ไขคือตรวจสอบสถานะผ่าน ::property.isInitialized ก่อนเข้าถึงในสถานการณ์ที่ไม่ชัดเจน

ปัญหาที่พบบ่อยที่สองคือการใช้ lateinit สำหรับคุณสมบัติที่มีความหมายเป็น val หากค่าถูกตั้งครั้งเดียวและไม่เคยเปลี่ยนแปลง lazy เป็นตัวเลือกที่ถูกต้องกว่า มันทำให้คุณสมบัติไม่เปลี่ยนแปลง ป้องกันการเขียนทับโดยไม่ตั้งใจ และเพิ่มความปลอดภัยของเธรดฟรี

ข้อผิดพลาดที่สามคือ lazy ที่มีผลข้างเคียง บล็อกการเริ่มต้น lazy ไม่ควรแก้ไขสถานะภายนอกหรือขึ้นอยู่กับลำดับการเริ่มต้นของคุณสมบัติ lazy อื่น เพราะลำดับการคำนวณขึ้นอยู่กับการเข้าถึงครั้งแรกและอาจไม่ชัดเจน หากคุณสมบัติ lazy อ้างอิงถึงกัน มันนำไปสู่การพึ่งพาแบบวงกลมและ StackOverflowError

ปัญหาที่สี่คือ หน่วยความจำรั่วผ่าน lazy ใน Android หากบล็อก lazy จับการอ้างอิงไปยัง Activity หรือ Fragment ตัวแทนจะเก็บการปิดไว้และตัวเก็บขยะไม่สามารถปล่อยคอมโพเนนต์ได้แม้หลังจากการทำลาย วิธีแก้คือใช้ lazy กับอ็อบเจกต์อายุสั้นเท่านั้นหรือส่งบริบท Application แทน Activity

ข้อผิดพลาดทั่วไปที่ห้าคือการพยายามใช้ lateinit กับชนิดพื้นฐาน คอมไพเลอร์ Kotlin ปิดกั้นสิ่งนี้ในระดับไวยากรณ์ แต่นักพัฒนาพยายามหลีกเลี่ยงข้อจำกัดผ่านตัวห่อ nullable สิ่งนี้นำไปสู่การตรวจสอบ null ที่ไม่จำเป็นและทำให้ข้อดีของการเริ่มต้นแบบเลื่อนเวลาเป็นโมฆะโดยสมบูรณ์

คำถามที่พบบ่อย

lateinit และ lazy ใน Kotlin แตกต่างกันอย่างไร?

lateinit เป็นตัวปรับแต่งสำหรับคุณสมบัติ var อนุญาตให้เริ่มต้นหลังคอนสตรักเตอร์ lazy เป็นตัวแทนสำหรับคุณสมบัติ val คำนวณค่าเมื่อเข้าถึงครั้งแรกและแคชไว้ lateinit ไม่รองรับชนิดพื้นฐานและ nullable ในขณะที่ lazy ปลอดภัยต่อเธรดโดยค่าเริ่มต้น

ฉันสามารถตรวจสอบได้หรือไม่ว่าคุณสมบัติ lateinit ถูกเริ่มต้นแล้ว?

ได้ ผ่านการอ้างอิงคุณสมบัติในตัว: ::propertyName.isInitialized เมธอดคืนค่า true หากคุณสมบัติถูกเริ่มต้นแล้ว นี่เป็นวิธีที่ปลอดภัยเพียงวิธีเดียวในการหลีกเลี่ยง UninitializedPropertyAccessException เมื่อทำงานกับฟิลด์ lateinit

ทำไม lateinit ไม่สามารถใช้กับชนิดพื้นฐาน?

ชนิดพื้นฐาน — Int, Double, Boolean และอื่น ๆ — ถูกคอมไพล์เป็นพื้นฐาน JVM (int, double, boolean) ซึ่งไม่มีสถานะ “ไม่ได้เริ่มต้น” lateinit ใช้ null เป็นตัวบ่งชี้ และพื้นฐานไม่สามารถเป็น null ดังนั้นกลไกนี้จึงเป็นไปไม่ได้ทางกายภาพสำหรับชนิดเหล่านี้

โหมดความปลอดภัยของเธรดค่าเริ่มต้นของ lazy คืออะไร?

ค่าเริ่มต้นคือ LazyThreadSafetyMode.SYNCHRONIZED — การตรวจสอบซ้อนกับการล็อก รับประกันการเริ่มต้นครั้งเดียวภายใต้การเข้าถึงพร้อมกันจากหลายเธรด สำหรับสถานการณ์เธรดเดียวใช้ NONE สำหรับการแข่งขันสูงใช้ PUBLICATION

เมื่อใดควรใช้ lateinit แทน lazy ใน Android?

เมื่อคุณสมบัติต้องเปลี่ยนหลังจากการเริ่มต้นหรือการสร้างถูกจัดการโดยเฟรมเวิร์ก ตัวอย่างทั่วไปคือ View Binding ใน Android Activity: binding ถูกสร้างใน onCreate และต้องเป็น var สำหรับพึ่งพา val ที่เริ่มต้นครั้งเดียวให้ใช้ lazy

สรุป

  • lateinit — ตัวปรับแต่งสำหรับคุณสมบัติ var ของ Kotlin อนุญาตให้เริ่มต้นหลังคอนสตรักเตอร์โดยไม่ต้อง nullable
  • lazy — ตัวแทนคุณสมบัติสำหรับ val ที่มีการคำนวณครั้งเดียวและแคชผลลัพธ์อัตโนมัติ
  • lateinit ขว้าง UninitializedPropertyAccessException เมื่อเข้าถึงก่อนการเริ่มต้น
  • lazy ปลอดภัยต่อเธรดโดยค่าเริ่มต้นผ่าน LazyThreadSafetyMode.SYNCHRONIZED
  • lateinit เข้ากันไม่ได้กับชนิดพื้นฐานและคุณสมบัติ nullable
  • lazy อาจทำให้หน่วยความจำรั่วใน Android เมื่อจับบริบทในการปิด
  • เลือก lateinit สำหรับคุณสมบัติที่เปลี่ยนแปลงได้และ lazy สำหรับพึ่งพา val ที่เริ่มต้นครั้งเดียว

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม