Inisialisasi tunda (lazy initialization) — mekanisme di Kotlin di mana properti objek diinisialisasi bukan pada saat pembuatan, tetapi pada akses pertama ke properti tersebut. Menurut data JetBrains, 2024, lateinit dan lazy — dua alat bawaan untuk mengimplementasikan strategi ini. Keduanya memecahkan masalah penundaan inisialisasi, tetapi berbeda secara fundamental dalam mekanisme kerja dan area penerapan.
Poin utama
Inisialisasi tunda — adalah pola di mana properti kelas menerima nilai bukan pada saat pembuatan objek, tetapi nanti, sesuai permintaan. Di Kotlin, pola ini diimplementasikan dengan dua cara yang secara fundamental berbeda: modifier lateinit dan delegasi lazy.
Kedua mekanisme memecahkan masalah umum — properti harus ada di kelas, tetapi nilainya belum diketahui pada saat pembuatan objek, atau perhitungannya terlalu memakan sumber daya untuk dilakukan tanpa kebutuhan. Menurut Google I/O 2023, hingga 40% properti dalam aplikasi Android tipikal dapat dioptimalkan melalui inisialisasi tunda, yang mengurangi waktu mulai sebesar 15–25%.
Pilihan antara lateinit dan lazy ditentukan oleh tiga faktor: keberubahubahan properti (var atau val), masa hidupnya (penugasan satu kali atau berulang), dan persyaratan thread-safe (akses satu atau banyak thread).
Skenario pertama dan paling umum — Dependency Injection. Framework (Dagger, Hilt, Koin) menyuntikkan dependensi setelah pembuatan objek, oleh karena itu properti tidak dapat diinisialisasi di konstruktor. Tanpa lateinit, kita harus mendeklarasikan semua dependensi sebagai nullable dan memeriksanya setiap kali digunakan.
Skenario kedua — sumber daya berat: basis data, klien jaringan, manajer file. Pembuatannya memerlukan waktu dan memori, oleh karena itu harus diinisialisasi hanya saat penggunaan aktual. lazy sangat ideal untuk kasus seperti itu, menjamin pembuatan satu kali.
Situasi ketiga — komponen Android (Activity, Fragment, ViewModel), yang siklus hidupnya dikelola oleh sistem operasi. Properti yang bergantung pada onCreate, onViewCreated, atau blok init ViewModel tidak dapat diinisialisasi di konstruktor.
lateinit — adalah modifier untuk properti var yang memungkinkan kompiler Kotlin menunda inisialisasi. Kompiler tidak memerlukan penugasan nilai di konstruktor, tetapi menghasilkan pemeriksaan runtime pada setiap akses: jika properti belum diinisialisasi, UninitializedPropertyAccessException dilempar.
class MainActivity {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
Batasan lateinit: properti harus dideklarasikan sebagai var (bukan val), non-nullable, bukan tipe primitif (Int, Double, Boolean, dll.). Alasannya — tipe primitif dikompilasi menjadi primitif JVM, yang tidak memiliki status «elum diinisialisasi». Untuk properti nullable, inisialisasi tunda tidak diperlukan: null sudah berarti tidak adanya nilai.
Untuk memeriksa status properti lateinit, digunakan referensi bawaan melalui operator ::: ::propertyName.isInitialized. Ini adalah satu-satunya cara aman untuk memeriksa apakah properti telah diinisialisasi, tanpa risiko mendapatkan pengecualian. Pemeriksaan hanya tersedia dari kelas yang sama atau kelas dalam, bukan dari kode eksternal.
class LoginFragment {
lateinit var binding: FragmentLoginBinding
fun isReady(): Boolean {
return ::binding.isInitialized
}
}
lateinit tidak menambah overhead setelah inisialisasi: setelah penugasan nilai, akses ke properti identik dengan referensi langsung ke bidang. Satu-satunya biaya — pemeriksaan inisialisasi pada setiap pembacaan sebelum penugasan. Setelah inisialisasi, kompiler JIT mengoptimalkan pemeriksaan.
Fitur penting: properti lateinit tidak dapat digunakan di kelas inline dan tidak didukung untuk properti dengan getter/setter khusus. Jika properti memerlukan akses terhitung — gunakan lazy sebagai pengganti lateinit.
lazy — adalah delegasi properti, yang terintegrasi dalam pustaka standar Kotlin. Ia menghitung nilai pada akses pertama ke properti dan menyimpan hasilnya untuk semua pemanggilan berikutnya. Tidak seperti lateinit, lazy hanya bekerja dengan val, membuat properti tidak dapat diubah setelah inisialisasi.
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 menerima parameter opsional LazyThreadSafetyMode, yang mengontrol mekanisme thread-safe. Secara default, SYNCHRONIZED digunakan — pemeriksaan ganda dengan penguncian (Double-Checked Locking), yang menjamin inisialisasi satu kali bahkan saat akses bersamaan dari banyak thread.
Mode PUBLICATION memungkinkan inisialisasi paralel: banyak thread dapat secara bersamaan menjalankan blok inisialisasi, tetapi hasilnya hanya akan diterima dari thread pertama yang selesai. Ini lebih cepat dari SYNCHRONIZED pada konkurensi tinggi, tetapi meningkatkan konsumsi sumber daya.
Mode NONE sepenuhnya menonaktifkan sinkronisasi. Gunakan hanya untuk properti yang aksesnya dijamin terjadi dari satu thread. Dalam mode ini, lazy bekerja dengan overhead minimal — praktis seperti penugasan langsung.
val heavyConfig: Config by lazy(LazyThreadSafetyMode.NONE) {
Config.loadFromFile("config.json")
}
lazy adalah pilihan yang tepat untuk dependensi yang diinisialisasi satu kali: repositori, klien jaringan, cache, basis data. Semantik val melindungi dari penimpaan tidak sengaja, dan thread-safe default membuat kode aman di lingkungan multi-thread. lazy juga bekerja dengan benar dengan tipe primitif, yang tidak mungkin dilakukan dengan lateinit.
Di Android, lazy sering digunakan untuk inisialisasi dependensi ViewModel melalui by viewModels() atau untuk membuat klien Retrofit. Namun, berhati-hatilah: jika blok lazy menangkap referensi ke Activity atau Fragment, ini dapat menyebabkan kebocoran memori, karena delegasi menyimpan penutupan hingga akhir masa pakai properti.
Pilihan antara lateinit dan lazy — bukan masalah preferensi, tetapi keputusan arsitektural yang ditentukan oleh sifat properti. Setiap mekanisme menyelesaikan tugasnya sendiri, dan area penerapannya hanya tumpang tindih sebagian.
| Kriteria | lateinit | lazy |
|---|---|---|
| Tipe properti | hanya var | hanya val |
| Nullable | dilarang | diizinkan |
| Tipe primitif | dilarang | diizinkan |
| Thread-safe | tidak dijamin | SYNCHRONIZED default |
| Pemeriksaan status | ::x.isInitialized | tidak diperlukan |
| Eksepsi pada kesalahan | UninitializedPropertyAccessException | kesalahan di blok inisialisasi |
| Caching | tidak diterapkan | perhitungan satu kali |
| Android Binding | View Binding, Data Binding | tidak digunakan |
| Framework DI | Dagger, Hilt, Koin | injeksi manual |
Gunakan lateinit ketika properti harus berubah setelah inisialisasi atau pembuatannya dikelola oleh kode eksternal. Contoh tipikal — View Binding di Android Activity: binding dibuat di onCreate, tetapi tetap var, karena framework tidak mendukung val untuk skenario ini.
Gunakan lazy ketika properti diinisialisasi satu kali, perhitungannya mahal, dan nilainya tidak berubah selama masa hidup objek. Contoh klasik — pembuatan malas klien Retrofit atau basis data Room pada akses pertama ke repositori.
Dalam satu kelas, kedua mekanisme dapat digunakan secara bersamaan. Misalnya, lateinit untuk View Binding dan lazy untuk repositori. Ini adalah praktik normal, yang mencerminkan persyaratan berbeda untuk properti yang berbeda. Yang utama — jangan mencampuradukkan semantik: jangan gunakan lateinit di mana val diperlukan dan jangan gunakan lazy untuk properti yang harus ditimpa.
Kesalahan paling umum dengan lateinit — mengakses properti sebelum diinisialisasi. Ini menyebabkan UninitializedPropertyAccessException, yang tidak tertangkap pada fase kompilasi, karena Kotlin mempercayai pengembang untuk urutan inisialisasi yang benar. Solusinya — selalu periksa status melalui ::property.isInitialized sebelum akses dalam situasi yang tidak jelas.
Masalah umum kedua — menggunakan lateinit untuk properti yang secara semantik adalah val. Jika nilai ditetapkan satu kali dan tidak berubah lagi, lazy adalah pilihan yang lebih tepat. Ia membuat properti tidak dapat diubah, mengecualikan penimpaan tidak sengaja, dan menambahkan thread-safe secara gratis.
Kesalahan ketiga — lazy dengan efek samping. Blok inisialisasi lazy tidak boleh mengubah status eksternal atau bergantung pada urutan inisialisasi properti lazy lainnya, karena urutan perhitungan tergantung pada akses pertama dan mungkin tidak jelas. Jika properti lazy saling merujuk, ini menyebabkan ketergantungan siklis dan StackOverflowError.
Masalah keempat — kebocoran memori melalui lazy di Android. Jika blok lazy menangkap referensi ke Activity atau Fragment — delegasi menyimpan penutupan, dan pengumpul sampah tidak dapat membebaskan komponen bahkan setelah komponen tersebut dihancurkan. Solusi — gunakan lazy hanya dengan objek berumur pendek atau berikan konteks Application, bukan Activity.
Kesalahan umum kelima — mencoba menerapkan lateinit ke tipe primitif. Kompiler Kotlin memblokir ini pada tingkat sintaks, tetapi pengembang mencoba menghindari batasan melalui pembungkus nullable. Ini menyebabkan pemeriksaan null yang tidak perlu dan sepenuhnya meniadakan keunggulan inisialisasi tunda.
Pertanyaan yang sering diajukan
lateinit — modifier untuk properti var, yang memungkinkan inisialisasi setelah konstruktor. lazy — delegasi untuk properti val, yang menghitung nilai pada akses pertama dan menyimpannya. lateinit tidak mendukung tipe primitif dan nullable, sedangkan lazy secara default thread-safe.
Ya, melalui referensi bawaan ke properti: ::propertyName.isInitialized. Metode mengembalikan true jika properti sudah diinisialisasi. Ini adalah satu-satunya cara aman untuk menghindari UninitializedPropertyAccessException saat bekerja dengan bidang lateinit.
Tipe primitif — Int, Double, Boolean dan lainnya — dikompilasi menjadi primitif JVM (int, double, boolean) yang tidak memiliki status «elum diinisialisasi». lateinit menggunakan null sebagai bendera, dan primitif tidak bisa null, oleh karena itu mekanisme secara fisik tidak dapat direalisasikan untuk tipe ini.
Secara default, LazyThreadSafetyMode.SYNCHRONIZED digunakan — pemeriksaan ganda dengan penguncian, yang menjamin inisialisasi satu kali saat akses dari banyak thread. Untuk skenario single-thread, gunakan NONE, untuk konkurensi tinggi — PUBLICATION.
Ketika properti harus berubah setelah inisialisasi atau pembuatannya dikelola oleh framework. Contoh tipikal — View Binding di Android Activity: binding dibuat di onCreate dan harus var. Untuk dependensi val yang diinisialisasi satu kali, gunakan lazy.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga