Tipe nullable menuntut setiap pemakaian ditangani, dan ketentuan itu terasa berat untuk properti yang sebenarnya pasti terisi, hanya saja pengisiannya menunggu waktu yang tepat. Kotlin menyediakan dua pola inisialisasi yang ditunda tanpa menjadikan properti nullable, yaitu modifier lateinit pada properti kelas dan fungsi lazy untuk nilai yang dihitung sekali. Topik ini membahas cara kerja keduanya, risikonya, dan cara memilih di antara keduanya.
Menunda inisialisasi dengan lateinit#
Modifier lateinit dipasang pada properti var bertipe non-null untuk menunda pengisiannya, sementara compiler dijanjikan bahwa properti tersebut terisi sebelum dipakai. Bentuk ini lazim pada properti kelas yang nilainya disiapkan setelah objek dibuat, misalnya data yang dimuat dari sumber luar. Properti semacam itu tidak perlu dinyatakan nullable, sehingga pemakaiannya setelah terisi bebas dari penanganan null. Program berikut memuat data ke dalam kelas statistik sebelum nilainya dihitung.
Buat berkas main.kt berikut.
class StatistikNilai {
lateinit var data: List<Int>
fun muat(nilai: List<Int>) {
data = nilai
}
fun rataRata(): Double {
return data.average()
}
fun sudahDiisi(): Boolean {
return ::data.isInitialized
}
}
fun main() {
val statistik = StatistikNilai()
statistik.muat(listOf(70, 80, 90))
println("Rata-rata: ${statistik.rataRata()}")
println("Sudah diisi: ${statistik.sudahDiisi()}")
val kosong = StatistikNilai()
println("Sudah diisi: ${kosong.sudahDiisi()}")
}Dua baris export pada perintah berikut mengarahkan terminal ke compiler Kotlin yang dipakai jalur ini, dan jalurnya perlu disesuaikan dengan lokasi instalasi pada komputer lain.
# compiler Kotlin dan pustaka standar yang dipakai jalur ini
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi berkas main.kt menjadi out.jar, lalu jalankan programnya
kotlinc main.kt -d out.jar && java -cp "out.jar:$STDLIB" MainKtRata-rata: 80.0
Sudah diisi: true
Sudah diisi: falseProperti data dideklarasikan lateinit var bertipe List<Int>, sehingga tidak perlu diisi pada saat objek dibuat dan tidak perlu dinyatakan List<Int>?. Metode muat mengisinya, dan setelah itu pemanggilan data.average() berjalan tanpa pemeriksaan apa pun. Fungsi sudahDiisi menunjukkan cara menanyakan keadaan properti, yaitu ::data.isInitialized yang menghasilkan true atau false tanpa menyentuh nilainya. Keluaran memperlihatkan keduanya, yaitu true untuk objek yang datanya sudah dimuat dan false untuk objek yang baru dibuat. Pemeriksaan ini berguna ketika status pengisian memang perlu ditanyakan, misalnya sebelum menampilkan ringkasan.
Alternatif tanpa lateinit adalah mendeklarasikan data sebagai List<Int>? yang diisi null pada awalnya. Setiap pemakaiannya kemudian menuntut safe call atau pemeriksaan, padahal kekosongannya memang tidak dimaksudkan sebagai keadaan yang sah. Modifier lateinit menyatakan niat itu kepada compiler, sehingga pemakaiannya setelah terisi sama bersihnya dengan properti biasa.
Akses sebelum inisialisasi#
Janji pada lateinit tidak diperiksa compiler, sehingga pemanggilan sebelum pengisian baru terungkap pada waktu program berjalan. Berkas berikut mengakses properti yang belum pernah diisi.
class JadwalPiket {
lateinit var petugas: String
}
fun main() {
val jadwal = JadwalPiket()
println(jadwal.petugas)
}# kompilasi berkas belum.kt, lalu jalankan dengan keluaran dibatasi empat baris pertama
kotlinc belum.kt -d belum.jar && java -cp "belum.jar:$STDLIB" BelumKt 2>&1 | head -4Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property petugas has not been initialized
at JadwalPiket.getPetugas(belum.kt:2)
at BelumKt.main(belum.kt:7)
at BelumKt.main(belum.kt)Program berhenti dengan UninitializedPropertyAccessException, dan pesannya menyebut nama properti yang belum diisi, sehingga penyebab kegagalannya langsung terbaca. Empat baris pertama dari stack trace ditampilkan agar keluarannya ringkas. Pesan semacam ini lebih informatif daripada NullPointerException biasa karena menunjuk propertinya, tetapi programnya tetap terhenti. Konsekuensinya, lateinit hanya layak dipakai bila urutan pengisian dikendalikan dengan pasti, misalnya oleh siklus hidup yang jelas pada kelas itu sendiri. Bila kepastian itu tidak ada, tanyakan keadaannya dengan isInitialized terlebih dahulu, atau nyatakan propertinya sebagai nullable dengan penanganan yang tertulis.
Inisialisasi sekali dengan lazy#
Fungsi lazy bekerja pada sisi berlawanan, yaitu menunda perhitungan nilai val hingga nilai itu pertama kali dipakai. Bentuknya by lazy { }, dengan blok yang dijalankan hanya pada akses pertama dan hasilnya disimpan untuk pemakaian berikutnya. Program berikut menyisipkan println di dalam blok lazy sebagai bukti bahwa perhitungannya hanya berlangsung sekali.
fun main() {
val dataUjian = listOf(70, 80, 90)
val rataRata: Double by lazy {
println("Rata-rata dihitung")
dataUjian.average()
}
println("Program dimulai")
println("Rata-rata: $rataRata")
println("Rata-rata lagi: $rataRata")
}# kompilasi berkas lazy.kt menjadi lazy.jar, lalu jalankan programnya
kotlinc lazy.kt -d lazy.jar && java -cp "lazy.jar:$STDLIB" LazyKtProgram dimulai
Rata-rata dihitung
Rata-rata: 80.0
Rata-rata lagi: 80.0Keluaran memperlihatkan urutan yang penting. Pesan Rata-rata dihitung muncul setelah Program dimulai, yang berarti blok lazy tidak dijalankan pada saat deklarasi, melainkan pada akses pertama. Pada pemakaian kedua, pesan tersebut tidak muncul lagi, sehingga nilai cukup diambil dari yang tersimpan. Pola ini menghemat pekerjaan ketika perhitungannya mahal dan tidak selalu diperlukan, misalnya menghitung statistik dari daftar nilai yang panjang. Kata kunci by di antara properti dan lazy { } merupakan bentuk delegasi, yaitu mekanisme penyerahan pekerjaan pengisian properti kepada objek lain.
Tipe yang dihasilkan bloknya bebas, mulai dari angka, teks, hingga objek yang kompleks, dan lazy juga sah untuk nilai bertipe nullable meskipun pemakaiannya paling cocok untuk nilai yang pasti terisi setelah dihitung. Yang mengikat adalah sifat val-nya, yaitu hasil pertama tersimpan selamanya dan bloknya tidak pernah dijalankan ulang.
Memilih antara lateinit dan lazy#
Kedua pola menjawab kebutuhan yang berbeda, dan tabel berikut merangkum perbedaannya.
| Aspek | lateinit | lazy |
|---|---|---|
| Properti yang dipakai | var | val |
| Sumber nilai | diisi dari luar oleh kode lain | dihitung sendiri dari data yang ada |
| Pekerjaan yang ditunda | penugasan nilai oleh kode lain | perhitungan nilai pada akses pertama |
| Bila dipakai terlalu dini | UninitializedPropertyAccessException | perhitungan dijalankan saat itu juga |
lateinit dipilih ketika nilainya berasal dari luar kelas dan pengisiannya bergantung pada urutan kejadian, sedangkan lazy dipilih ketika nilai dapat dihitung sendiri dari data yang sudah ada. Properti lateinit mempertahankan sifat var-nya, sehingga nilainya boleh diganti sepanjang usia objek, sedangkan properti lazy berupa val yang diamankan sejak akses pertamanya. Pada keduanya, properti tidak perlu dinyatakan nullable, sehingga kode pemakainya bebas dari penanganan null. Apabila urutan pengisiannya tidak dapat dijamin dan nilainya tidak dapat dihitung sendiri, nilai nullable dengan penanganan yang tertulis tetap menjadi pilihan yang paling aman.
Pada praktiknya, lateinit sering ditemui pada properti yang pengisiannya dikelola framework, sedangkan lazy sering dipakai untuk konfigurasi atau hasil perhitungan yang berat tetapi jarang dipakai. Keduanya menghilangkan kebutuhan variabel sementara yang harus diisi secara manual pada saat objek dibuat.
Ringkasan#
lateinitmenunda pengisian propertivarbertipe non-null, dan keadaannya dapat ditanyakan denganisInitialized.- Mengakses properti
lateinitsebelum diisi menghentikan program denganUninitializedPropertyAccessExceptionyang menyebut nama propertinya. by lazy { }menunda perhitungan nilaivalhingga akses pertama, lalu menyimpan hasilnya untuk pemakaian berikutnya.lateinituntuk nilai dari luar yang pengisiannya bergantung pada urutan kejadian, sedangkanlazyuntuk nilai yang dihitung sendiri sekali.
Referensi#
- Properties — Kotlin Documentation — Dokumentasi resmi properti lateinit beserta syaratnya dan pemeriksaan isInitialized.
- Delegated properties — Kotlin Documentation — Penjelasan resmi pola lazy beserta perilaku sinkronisasinya.