Beban startup aplikasi sering meningkat karena banyak property mahal (koneksi database, cache, kalkulasi berat) dibangun lebih dulu meski belum tentu dipakai. Kotlin menyelesaikannya lewat function lazy yang mengembalikan delegate Lazy<T> — nilai hanya dievaluasi saat property pertama kali dibaca. Pada chapter ini kita akan membahas sintaksis lazy, tiga mode thread-safety yang ditawarkannya, serta cara mengukur efisiensi yang diperoleh dari caching otomatis. Kode contoh akan memperlihatkan kapan lambda dieksekusi dan kapan hasilnya diambil dari cache internal delegate.

Bentuk Dasar lazy #

Function lazy menerima lambda dan mengembalikan objek Lazy<T> yang mengimplementasikan getValue. Karena itu, ekspresi by lazy { ... } langsung dapat dipakai pada property val:

kotlin Bentuk umum lazy
val database: String by lazy {
    println("membuka koneksi...")
    "jdbc:postgresql://localhost/app"
}

Lambda hanya dievaluasi sekali. Hasilnya disimpan internally oleh delegate dan dikembalikan pada setiap akses berikutnya tanpa menjalankan lambda lagi. Inilah perbedaan utama dengan function biasa yang menjalankan tubuhnya setiap kali dipanggil.

Tiga Mode Thread-Safety #

lazy menerima parameter opsional LazyThreadSafetyMode yang menentukan bagaimana delegate berperilaku di lingkungan multi-thread:

ModeSinkronisasiCocok untuk
SYNCHRONIZED (default)synchronized block pada delegateMulti-thread, ingin evaluasi tepat sekali.
PUBLICATIONTanpa lock; beberapa thread boleh menjalankan lambda, hanya satu hasil yang dipakaiKomputasi murah, idempoten, bisa dievaluasi paralel.
NONETanpa lock, tanpa jaminan apa punSingle-thread, performa tertinggi.

SYNCHRONIZED adalah pilihan paling aman ketika kita tidak yakin property akan diakses dari thread mana. PUBLICATION berguna bila lambda tidak memiliki efek samping berbahaya sehingga evaluasi ganda masih aman. NONE dipakai pada kode yang dijamin hanya berjalan di satu thread (misalnya di dalam coroutine dispatcher tunggal) untuk menghemat overhead lock.

Contoh Kode #

File lazy_delegation.kt membandingkan ketiga mode dan mengukur waktu akses pertama vs akses kedua:

kotlin
// File: lazy_delegation.kt
// Mendemonstrasikan lazy delegation: inisialisasi sekali saat akses pertama.
package com.example.lazyprop

import kotlin.system.measureNanoTime

class KonfigurasiMahal {
    // Lazy default: thread-safe, mode SYNCHRONIZED.
    val database: String by lazy {
        println("[lazy] Menginisialisasi database...")
        Thread.sleep(50)
        "jdbc:postgresql://localhost/app"
    }

    // Lazy mode PUBLICATION: boleh dievaluasi berkali-kali di thread berbeda,
    // tetapi hanya satu nilai yang dipakai.
    val cacheKey: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
        println("[lazy] Menghitung cacheKey...")
        "key-123456"
    }

    // Lazy mode NONE: tidak aman untuk multi-thread, tercepat.
    val nilaiCache: Int by lazy(LazyThreadSafetyMode.NONE) {
        println("[lazy] Menghitung nilaiCache...")
        42
    }
}

fun main() {
    val cfg = KonfigurasiMahal()

    // Akses pertama — lazy dieksekusi sekali.
    val t1 = measureNanoTime { println("Akses pertama database: ${cfg.database}") }
    // Akses kedua — tidak ada eksekusi ulang.
    val t2 = measureNanoTime { println("Akses kedua database: ${cfg.database}") }

    println()
    println("Waktu akses pertama: ${t1 / 1_000_000} ms")
    println("Waktu akses kedua: ${t2 / 1_000} microseconds (sangat cepat — sudah di-cache)")

    println()
    println("cacheKey: ${cfg.cacheKey}")
    println("cacheKey (lagi): ${cfg.cacheKey}")

    println()
    println("nilaiCache: ${cfg.nilaiCache}")
}

Output yang Dihasilkan:

text
[lazy] Menginisialisasi database...
Akses pertama database: jdbc:postgresql://localhost/app
Akses kedua database: jdbc:postgresql://localhost/app

Waktu akses pertama: 50 ms
Waktu akses kedua: 24 microseconds (sangat cepat — sudah di-cache)

[lazy] Menghitung cacheKey...
cacheKey: key-123456
cacheKey (lagi): key-123456

[lazy] Menghitung nilaiCache...
nilaiCache: 42

Mari kita bedah kode di atas baris-per-baris:

  1. Baris 9-13 (val database by lazy { ... Thread.sleep(50) ... }): lambda mensimulasikan pekerjaan mahal lewat Thread.sleep(50). Karena tidak ada argumen LazyThreadSafetyMode, default SYNCHRONIZED dipakai. Lambda dijalankan satu kali pada akses pertama dan hasilnya di-cache.
  2. Baris 17-20 (val cacheKey by lazy(LazyThreadSafetyMode.PUBLICATION) { ... }): contoh mode PUBLICATION. Cocok bila lambda ringan, idempoten, dan kita tidak keberatan bila beberapa thread sempat menjalankan lambda secara paralel. Pada contoh single-thread ini, lambda tetap dieksekusi sekali karena setelah hasil tersimpan, akses kedua langsung mengembalikan nilai cache.
  3. Baris 23-26 (val nilaiCache by lazy(LazyThreadSafetyMode.NONE) { ... }): mode tercepat karena tidak ada lock. Hanya aman bila kita yakin property diakses dari satu thread. Memakainya pada kode multi-thread berisiko menghasilkan kondisi balapan (race condition).
  4. Baris 33-39 (measureNanoTime): mengukur waktu akses pertama vs kedua dalam nanodetik. Pembagian t1 / 1_000_000 mengonversi ke milidetik, sedangkan t2 / 1_000 mengonversi ke mikrodetik. Output memperlihatkan perbedaan ordo besar — akses kedua jauh lebih cepat karena lambda tidak dijalankan ulang.
  5. Baris 42-46: akses cacheKey dan nilaiCache. Masing-masing hanya mencetak pesan [lazy] Menghitung ... sekali. Hal ini membuktikan bahwa delegate Lazy menyimpan hasilnya secara permanen selama instance pemilik masih hidup.

Kapan Memakai lazy #

lazy paling berguna ketika suatu property mungkin tidak selalu dipakai pada setiap jalur eksekusi. Misalnya, class Repository dengan beberapa method bisa saja hanya memanggil satu method tertentu; bila property koneksi dibuat eagerly di konstruktor, koneksi akan selalu dibuka walaupun tidak terpakai. Dengan lazy, koneksi hanya dibuka saat method yang membutuhkan benar-benar dipanggil.

Referensi Lanjutan #

Selanjutnya #

Setelah lazy, chapter berikutnya akan membahas delegate Delegates.observable — pola yang memanggil callback setiap kali nilai property berubah, sangat berguna untuk audit log dan reaksi otomatis terhadap perubahan state.