Init block adalah blok kode yang dieksekusi saat sebuah object dibuat, tepat setelah primary constructor selesai menugaskan parameter ke property. Berbeda dengan Java yang menempatkan logika constructor di dalam badan constructor, Kotlin memisahkan deklarasi property dari logika inisialisasi melalui init block. Pada chapter ini kita akan memahami urutan eksekusi init block terhadap property initializer, perilaku multiple init block, serta cara memanfaatkan init block untuk validasi.

Kapan Init Block Dieksekusi #

Saat sebuah object dibuat dengan Produk("Laptop", 1500000), kompilator menjalankan tahapan berurutan berikut:

  1. Mengalokasikan memori untuk object baru.
  2. Menugaskan argumen constructor ke parameter nama dan hargaAwal.
  3. Mengeksekusi property initializer dan init block sesuai urutan deklarasi dari atas ke bawah.
  4. (Bila ada) menjalankan body secondary constructor.
  5. Mengembalikan referensi object ke pemanggil.

Karena init block berbagi urutan dengan property initializer, posisi deklarasi sangat menentukan kapan kode di dalamnya dijalankan. Sebuah property yang dideklarasikan setelah sebuah init block belum memiliki nilai saat init block tersebut dieksekusi.

Multiple Init Block dalam Urutan Deklarasi #

Kotlin mengizinkan sebuah class memiliki lebih dari satu init block. Semua init block dieksekusi sesuai urutan kemunculan dalam kode sumber, berseling dengan property initializer yang berada di antara mereka. Pola ini sering dipakai untuk mengelompokkan logika inisialisasi yang berbeda secara semantik.

kotlin Contoh: kerangka beberapa init block
class Contoh(a: Int) {
    val x: Int = a + 1

    init {
        println("Init pertama, x=$x")
    }

    val y: Int = x * 2

    init {
        println("Init kedua, y=$y")
    }
}

Saat object dibuat, urutan eksekusi adalah: x dihitung, init pertama dijalankan, y dihitung, init kedua dijalankan. Inisialisasi tidak pernah melompat-lompat terlepas dari letak deklarasi.

Contoh Lengkap dengan Validasi #

Contoh berikut memperlihatkan class Produk dengan tiga init block yang berbagi tugas: pencatatan, perhitungan property, dan validasi. Property diskon dideklarasikan di antara init block untuk membuktikan urutan eksekusi yang sesuai posisi deklarasi.

kotlin
// init_blocks.kt
// Mendemonstrasikan beberapa init block yang dieksekusi berurutan dan validasi.

class Produk(val nama: String, hargaAwal: Int) {
    val harga: Int

    init {
        println("Init block 1: nama=$nama, hargaAwal=$hargaAwal")
    }

    val diskon: Int = if (hargaAwal > 100000) 10 else 0

    init {
        println("Init block 2: diskon dihitung = $diskon%")
        harga = if (hargaAwal < 0) 0 else hargaAwal
    }

    init {
        require(harga >= 0) { "Harga tidak boleh negatif: $harga" }
        println("Init block 3: validasi selesai untuk $nama")
    }

    fun hargaAkhir(): Int = harga - (harga * diskon / 100)
}

fun main() {
    println("Membuat produk A")
    val a = Produk("Laptop", 1500000)
    println("Harga akhir A: ${a.hargaAkhir()}")
    println()

    println("Membuat produk B")
    val b = Produk("Buku Tulis", 5000)
    println("Harga akhir B: ${b.hargaAkhir()}")
}

Output yang Dihasilkan:

text
Membuat produk A
Init block 1: nama=Laptop, hargaAwal=1500000
Init block 2: diskon dihitung = 10%
Init block 3: validasi selesai untuk Laptop
Harga akhir A: 1350000

Membuat produk B
Init block 1: nama=Buku Tulis, hargaAwal=5000
Init block 2: diskon dihitung = 0%
Init block 3: validasi selesai untuk Buku Tulis
Harga akhir B: 5000

Mari kita amati urutan eksekusinya:

  1. Produk("Laptop", 1500000) dipanggil: primary constructor menerima nama="Laptop" dan hargaAwal=1500000.
  2. Init block 1 dieksekusi: mencatat nama dan hargaAwal. Property diskon belum dihitung karena dideklarasikan setelah init block ini.
  3. val diskon: Int = if (hargaAwal > 100000) 10 else 0: property initializer dievaluasi. Karena hargaAwal = 1500000 > 100000, diskon = 10.
  4. Init block 2 dieksekusi: mencatat diskon yang sudah dihitung. Body menugaskan property harga berdasarkan hargaAwal; karena hargaAwal >= 0, harga = 1500000.
  5. Init block 3 dieksekusi: memanggil require(harga >= 0) yang melempar IllegalArgumentException bila kondisi gagal. Karena harga = 1500000 >= 0, validasi lolos dan pesan sukses tercetak.
  6. a.hargaAkhir() dipanggil: menghitung 1500000 - (1500000 * 10 / 100) = 1350000. Property diskon dan harga sudah final.

Validasi dengan require dan check #

Kotlin menyediakan dua function standar untuk validasi di dalam init block: require(condition) { message } melempar IllegalArgumentException, dan check(condition) { message } melempar IllegalStateException. Keduanya lazim digunakan untuk menegakkan invarian class sejak object dibuat. Bila kondisi gagal, object tidak akan pernah dikembalikan ke pemanggil.

Urutan Deklarasi Memengaruhi Hasil #

Karena init block dan property initializer berbagi urutan, sebuah property yang dideklarasikan setelah init block belum siap dipakai di dalam init block tersebut. Bila kita menukar posisi val diskon dan init block 2 pada contoh di atas, referensi diskon di dalam init block 2 akan gagal dikompilasi karena compiler mendeteksi penggunaan sebelum deklarasi.

Aturan praktis: deklarasikan property yang dibutuhkan sebuah init block sebelum init block tersebut. Bila property bergantung pada hasil init block, deklarasikan setelahnya. Dengan urutan yang konsisten, kode menjadi lebih mudah dibaca dan perilakunya dapat diprediksi.

Referensi Lanjutan #

Ringkasan #

Init block adalah mekanisme utama Kotlin untuk menjalankan logika inisialisasi setelah primary constructor. Beberapa init block dieksekusi berurutan bersama property initializer sesuai posisi deklarasi. require dan check menjadi alat idiomatik untuk validasi di dalam init block. Pada chapter Property Accessors berikutnya, kita akan melihat bagaimana getter dan setter custom dapat melengkapi inisialisasi dengan kontrol lebih halus terhadap pembacaan dan penugasan property.