Variabel tidak hidup selamanya — ia hanya dapat diakses dalam wilayah kode tertentu yang disebut scope. Memahami scope membantu kita menulis kode yang rapi (variabel lokal hanya hidup saat dibutuhkan) dan menghindari bug akibat variabel yang tak sengaja bocor ke luar wilayahnya. Pada chapter ini kita akan membedah block scope { }, perilaku shadowing, loop variable scope, serta scope function run { } untuk membuat wilayah variabel eksplisit.
Block scope #
Di Kotlin, sebuah blok kode { ... } membentuk scope baru. Variabel yang dideklarasikan di dalam blok tidak dapat diakses di luar blok tersebut. Sebaliknya, variabel di luar blok tetap dapat diakses dari dalam blok.
val pesan = "halo" // scope luar
run {
val pesanLokal = "dalam blok" // scope dalam
println(pesan) // sah: akses scope luar
println(pesanLokal) // sah
}
// println(pesanLokal) // galat: pesanLokal tidak terlihat di siniVariabel lokal dalam blok hanya hidup selama blok tersebut dieksekusi. Setelah blok berakhir, referensi ke variabel tersebut tidak lagi valid dan memori dapat diklaim kembali oleh garbage collector.
Inner shadowing #
Kotlin memperbolehkan shadowing — mendeklarasikan variabel di scope dalam dengan nama yang sama dengan variabel di scope luar. Variabel luar disembunyikan (di-shadow) oleh variabel dalam selama blok dalam dieksekusi.
val nama = "Budi"
run {
val nama = "Siti" // shadow variabel luar
println(nama) // Siti
}
println(nama) // Budi (kembali ke scope luar)Meskipun sah secara sintaks, shadowing membuat kode sulit dibaca. Pembaca bisa keliru mengira variabel dalam adalah variabel luar.
Loop variable scope #
Variabel yang dideklarasikan dalam loop (misalnya for (i in 1..3) { val item = ... }) hanya hidup untuk satu iterasi. Pada iterasi berikutnya, variabel tersebut dianggap baru.
for (i in 1..3) {
val item = "item-$i" // baru setiap iterasi
println(item)
}
// println(item) // galat: item tidak terlihatVariabel i dari loop juga hanya hidup dalam loop — tidak dapat diakses setelah for selesai. Perilaku ini berlaku juga untuk while dan do-while.
Scope function run { } #
Kotlin menyediakan beberapa scope function untuk membentuk blok dengan wilayah variabel eksplisit. Yang paling umum dipakai untuk membentuk blok adalah run { }. Fungsi run menjalankan blok lambda dan mengembalikan hasil ekspresi terakhirnya.
val total = run {
val harga = 5000
val qty = 4
harga * qty // nilai kembalian
}
println(total) // 20000Variabel harga dan qty hanya hidup dalam blok run. Setelah blok berakhir, keduanya tidak dapat diakses — wilayah variabel bersih dan terbatas.
Demonstrasi scope dan lifetime #
Berikut program lengkap yang memperlihatkan berbagai perilaku scope:
val penggunaGlobal = "Budi"
fun main() {
val pesan = "Halo dari main"
println(pesan)
run {
val pesanBlok = "Halo dari blok dalam"
println(pesanBlok)
println("Akses pesan luar: $pesan")
}
println()
val totalBelanja = 100_000
run {
val diskon = 10_000
val totalSetelahDiskon = totalBelanja - diskon
println("Total belanja : $totalBelanja")
println("Diskon : $diskon")
println("Total setelah diskon: $totalSetelahDiskon")
}
println()
println("Loop variable scope:")
for (i in 1..3) {
val item = "item-$i"
println("Memproses $item")
}
println()
println("Shadowing (dapat dikompilasi tetapi tidak dianjurkan):")
val namaLuar = "Budi"
run {
val namaLuar = "Siti"
println("Di dalam run: namaLuar = $namaLuar")
}
println("Di luar run : namaLuar = $namaLuar")
println()
println("Lifetime setelah blok berakhir:")
run {
val temporary = listOf(1, 2, 3)
println("Di dalam blok: temporary.size = ${temporary.size}")
}
println()
println("Akses variabel global: penggunaGlobal = $penggunaGlobal")
println()
fun hitung(): Int {
val sementara = 42
return sementara * 2
}
println("Hasil hitung() = ${hitung()}")
}Output yang Dihasilkan:
Halo dari main
Halo dari blok dalam
Akses pesan luar: Halo dari main
Total belanja : 100000
Diskon : 10000
Total setelah diskon: 90000
Loop variable scope:
Memproses item-1
Memproses item-2
Memproses item-3
Shadowing (dapat dikompilasi tetapi tidak dianjurkan):
Di dalam run: namaLuar = Siti
Di luar run : namaLuar = Budi
Lifetime setelah blok berakhir:
Di dalam blok: temporary.size = 3
Akses variabel global: penggunaGlobal = Budi
Hasil hitung() = 84Mari kita amati baris-baris penting pada kode di atas:
- Baris 1 (
val penggunaGlobal = "Budi") — variabel top-level; scope-nya mencakup seluruh file. Dapat diakses darimaindan function lain di file yang sama. - Baris 4-5 (
val pesan = "Halo dari main"; println(pesan)) — variabel lokalpesanhidup dalammain. Setelahmainberakhir,pesantidak lagi valid. - Baris 7-11 (
run { val pesanBlok = ...; println(pesanBlok); println(pesan) }) — blokrunmembentuk scope dalam.pesanBlokhanya hidup di blok ini. Variabelpesandari scope luar tetap dapat diakses dari dalam. - Baris 14-20 (
val totalBelanja = 100_000; run { val diskon = ...; ... }) —totalBelanjadi-deklarasi di scopemain, dapat diakses dari dalamrun. Sebaliknya,diskondantotalSetelahDiskonhanya hidup dalam blokrun. - Baris 23-26 (
for (i in 1..3) { val item = ... }) — variabelidanitemhanya hidup dalam satu iterasi. Setiap iterasi membuat instance baru. - Baris 30-36 (
val namaLuar = "Budi"; run { val namaLuar = "Siti" }) — shadowing: variabel dalamrunmenyembunyikan variabel luar dengan nama yang sama. Setelahrunberakhir, variabel luar kembali terlihat. Kode dapat dikompilasi tetapi membingungkan — dianjurkan dihindari. - Baris 40-43 (
run { val temporary = listOf(1,2,3); println(...) }) — variabeltemporaryhanya hidup dalam blok. Setelah blok berakhir, referensitemporarykeluar dari scope; memori yang dipakai dapat di-garbage collect. - Baris 46 (
println("Akses variabel global: penggunaGlobal = $penggunaGlobal")) —penggunaGlobaldapat diakses dari mana pun dalam file. Top-level scope tidak pernah berakhir selama program berjalan. - Baris 49-52 (
fun hitung(): Int { val sementara = 42; return sementara * 2 }) — variabelsementarahanya hidup selama pemanggilan function. Setiap pemanggilanhitung()membuat instancesementarabaru.
Tabel ringkas scope #
| Wilayah | Scope variabel |
|---|---|
Top-level (val x di file) | Seluruh file, sepanjang program berjalan |
| Dalam function | Selama pemanggilan function |
Dalam blok { } atau run { } | Selama blok dieksekusi |
Dalam loop for/while | Satu iterasi |
| Dalam lambda parameter | Selama pemanggilan lambda |
Dalam try/catch | Selama blok dieksekusi |
Scope function lainnya #
Selain run, Kotlin menyediakan beberapa scope function dengan kombinasi cara akses dan nilai kembalian berbeda:
| Function | Receiver | Return | Kegunaan |
|---|---|---|---|
run { } | tidak ada (lambda) | Hasil lambda | Blok dengan scope variabel lokal |
let { } | it | Hasil lambda | Transformasi nullable |
apply { } | this | Receiver | Konfigurasi object |
also { } | it | Receiver | Side effect tanpa mengganggu chain |
with(x) { } | this (dari arg) | Hasil lambda | Operasi beruntun pada object |
Pembahasan lengkap scope function tersedia pada chapter khusus di section lanjutan.
Referensi Lanjutan #
- Properties
— dokumentasi properti Kotlin yang menjelaskan scope
val/vardan backing field - Coding conventions — panduan konvensi Kotlin untuk memilih scope yang tepat bagi variabel lokal dan properti
Pemahaman scope dan lifetime membantu kita menulis kode yang rapi dan minim bug. Berikutnya, kita akan menutup section ini dengan konvensi penamaan variabel — pada chapter Naming Conventions.