Pada chapter sebelumnya kita melihat bahwa nested class default bersifat statis terhadap outer — tanpa referensi ke instance outer, ia tidak dapat membaca property instance. Kadang kita justru membutuhkan akses tersebut: misalnya ketika sebuah helper harus membaca state outer atau memodifikasi list milik outer. Kotlin menyediakan keyword inner untuk membuat inner class — class bersarang yang secara implisit membawa referensi ke instance outer class. Pada chapter ini kita akan mendeklarasikan inner class, mengakses property outer dari dalam inner, dan memakai sintaks this@Outer untuk merujuk ke instance outer secara eksplisit.
Apa Itu Inner Class #
Inner class adalah class bersarang yang dideklarasikan dengan keyword inner sebelum class. Setiap instance inner class memiliki referensi implisit ke instance outer class yang membuatnya. Karena referensi tersebut, inner class dapat membaca property dan memanggil method instance milik outer seolah-olah anggota tersebut adalah miliknya sendiri.
Untuk membuat instance inner class, kita wajib memiliki instance outer terlebih dahulu. Sintaks pembuatannya adalah outerInstance.Inner() — berbeda dari nested class biasa yang dapat dipakai tanpa instance outer. Batasan ini mencerminkan ketergantungan struktural antara inner dan outer.
Deklarasi dan Pemakaian #
Contoh berikut mendefinisikan class Tumpukan dengan property kapasitas dan penyimpanan. Inner class Item membaca kedua property outer tersebut saat method dorong() dipanggil. Contoh juga menunjukkan inner class yang memodifikasi state outer (PenghitungHuruf menambah counter dihitung) dan akses eksplisit ke instance outer via this@Tumpukan.
// inner_classes.kt
// Mendemonstrasikan keyword `inner` — class bersarang yang membawa
// referensi ke instance outer class.
class Tumpukan(private val kapasitas: Int) {
private val penyimpanan = mutableListOf<String>()
// Inner class membawa referensi ke instance Tumpukan.
// Dapat mengakses property `kapasitas` dan `penyimpanan` milik outer.
inner class Item(private val nilai: String) {
fun dorong() {
if (penyimpanan.size >= kapasitas) {
println("Tumpukan penuh, tidak bisa menambah '$nilai'")
return
}
penyimpanan.add(nilai)
println("Menambahkan '$nilai' (isi: ${penyimpanan.size}/$kapasitas)")
}
fun aksesOuter(): Tumpukan = this@Tumpukan
}
fun ukuran(): Int = penyimpanan.size
fun cetakSemua() {
println("Isi tumpukan: ${penyimpanan.joinToString(", ")}")
}
}
class Kata(private val kata: String) {
private var dihitung = 0
inner class PenghitungHuruf {
fun hitung(target: Char): Int {
dihitung++
return kata.count { it == target }
}
fun berapaKaliDihitung(): Int = dihitung
}
}
class PetaWarna {
private val pasangan = mutableListOf<Pair<String, String>>()
inner class Pencari(private val kunci: String) {
fun temukan(): String? {
for ((k, v) in pasangan) {
if (k == kunci) return v
}
return null
}
fun tambahkan(nilai: String) {
pasangan.add(kunci to nilai)
}
}
}
fun main() {
val tumpukan = Tumpukan(kapasitas = 3)
val item1 = tumpukan.Item("Budi")
val item2 = tumpukan.Item("Siti")
val item3 = tumpukan.Item("Dewi")
val item4 = tumpukan.Item("Andi")
item1.dorong()
item2.dorong()
item3.dorong()
item4.dorong() // akan tertolak karena tumpukan penuh
println()
tumpukan.cetakSemua()
println()
// Akses instance outer dari dalam inner class.
val outer = item1.aksesOuter()
println("outer === tumpukan: ${outer === tumpukan}")
println()
val kata = Kata("belajar-kode")
val penghitung = kata.PenghitungHuruf()
println("Jumlah huruf 'a' dalam 'belajar-kode': ${penghitung.hitung('a')}")
println("Jumlah huruf 'e' dalam 'belajar-kode': ${penghitung.hitung('e')}")
println("Jumlah huruf 'z' dalam 'belajar-kode': ${penghitung.hitung('z')}")
println("Method hitung dipanggil sebanyak: ${penghitung.berapaKaliDihitung()}")
println()
val peta = PetaWarna()
val pencariMerah = peta.Pencari("merah")
pencariMerah.tambahkan("#FF0000")
val pencariHijau = peta.Pencari("hijau")
pencariHijau.tambahkan("#00FF00")
println("Cari 'merah' di peta: ${peta.Pencari("merah").temukan()}")
println("Cari 'biru' di peta: ${peta.Pencari("biru").temukan()}")
}Output yang Dihasilkan:
Menambahkan 'Budi' (isi: 1/3)
Menambahkan 'Siti' (isi: 2/3)
Menambahkan 'Dewi' (isi: 3/3)
Tumpukan penuh, tidak bisa menambah 'Andi'
Isi tumpukan: Budi, Siti, Dewi
outer === tumpukan: true
Jumlah huruf 'a' dalam 'belajar-kode': 2
Jumlah huruf 'e' dalam 'belajar-kode': 2
Jumlah huruf 'z' dalam 'belajar-kode': 0
Method hitung dipanggil sebanyak: 3
Cari 'merah' di peta: #FF0000
Cari 'biru' di peta: nullMari kita bedah kode di atas:
inner class Item(private val nilai: String): keywordinnermembuat classItemmembawa referensi ke instanceTumpukanyang membuatnya. Tanpainner, kodepenyimpanan.sizeakan gagal dikompilasi karenapenyimpananadalah property instance milikTumpukan.val item1 = tumpukan.Item("Budi"): instanceItemdibuat lewat instance outer. Tidak ada sintaksTumpukan.Item("Budi")tanpa instance outer, karena referensi outer wajib ada.if (penyimpanan.size >= kapasitas): di dalam methoddorong(), kedua namapenyimpanandankapasitasadalah property milik outerTumpukan, namun dapat dipanggil langsung tanpa prefiks karena inner class membawa referensi outer.penyimpanan.add(nilai): inner class tidak hanya membaca state outer, tetapi juga dapat memodifikasinya. SetelahItem.dorong()dipanggil, listpenyimpanandi instanceTumpukanberubah.fun aksesOuter(): Tumpukan = this@Tumpukan: sintaksthis@Outermengembalikan instance outer dari dalam inner class. Output menunjukkanouter === tumpukanbernilaitrue, membuktikan keduanya adalah instance yang sama.dihitung++di dalamPenghitungHuruf.hitung(): inner class memodifikasivar dihitungmilik outer. Setiap pemanggilanhitung()menambah counter, danberapaKaliDihitung()mengembalikan nilai counter tersebut — bukti inner class berbagi state dengan outer.Pencari("merah")danPencari("hijau"): dua instance inner class berbagi instance outerPetaWarna. Keduanya menambah dan membaca listpasanganmilik outer yang sama, sehingga data yang ditambahpencariMerahdapat ditemukan oleh instancePencarilain yang dibuat dari outer yang sama.
Akses Eksplisit ke Outer: this@Outer #
Ketika inner class memiliki property dengan nama yang sama dengan property outer, kita perlu membedakan keduanya secara eksplisit. Sintaks this@Outer mengacu pada instance outer class, sehingga [email protected] merujuk ke property outer sedangkan properti (tanpa prefiks) merujuk ke property inner bila ada.
Sintaks this@Outer juga berguna ketika kita ingin mengembalikan instance outer dari method inner class — misalnya untuk fluent builder yang mengembalikan outer setelah konfigurasi selesai.
Kapan Memilih Inner vs Nested #
Perbedaan utama dapat diringkas dalam tabel berikut:
| Aspek | Nested class (default) | Inner class (inner class) |
|---|---|---|
| Referensi outer | Tidak | Ya |
| Akses property instance outer | Tidak | Ya |
| Membuat instance tanpa outer | Bisa | Tidak |
| Beban memori | Lebih ringan | Lebih berat |
Pilih inner class hanya ketika helper benar-benar perlu membaca atau memodifikasi state outer. Untuk helper murni yang tidak peduli state outer, nested class biasa adalah pilihan yang lebih ringan.
Referensi Lanjutan #
- Nested classes — bagian inner class yang membawa referensi instance outer
Ringkasan #
Inner class dengan keyword inner membawa referensi implisit ke instance outer, sehingga inner dapat membaca dan memodifikasi anggota instance outer secara langsung. Instance inner wajib dibuat lewat instance outer via outer.Inner(), dan sintaks this@Outer memberi akses eksplisit ke instance outer dari dalam inner class. Pada chapter object Declarations berikutnya, kita akan melihat cara mendeklarasikan singleton langsung di tingkat class dengan keyword object.