Smart cast adalah salah satu fitur paling nyaman di Kotlin, tetapi fitur ini tidak bekerja dengan keajaiban — kompilator melakukan control flow analysis untuk melacak status setiap variabel di setiap titik program. Setelah if (x != null), kompilator tahu bahwa x non-nullable pada cabang tersebut. Setelah when (x) { is String -> ... }, kompilator menyempitkan tipe x menjadi String di dalam cabang. Pada chapter ini kita akan membedah bagaimana kompilator melacak null check, type check, dan assignment, lalu memanfaatkannya untuk menulis kode yang aman tanpa cast eksplisit.
Smart Cast setelah Null Check #
Saat kompilator melihat if (x != null), ia menandai x sebagai non-nullable di dalam cabang tersebut. Hal ini menghilangkan kebutuhan menulis x!!.length atau x?.length di dalam blok.
fun main() {
// Smart cast setelah null check
val nama: String? = "Budi"
if (nama != null) {
// kompilator tahu nama non-null di sini
println("Panjang nama: ${nama.length}")
println("Huruf besar: ${nama.uppercase()}")
}
// Smart cast tipe setelah is check
val nilai: Any = 42
if (nilai is Int) {
// kompilator mengetahui nilai adalah Int di cabang ini
val kuadrat: Int = nilai * nilai
println("Kuadrat: $kuadrat")
}
// Pencabangan dengan else: kompilator melacak kondisi terbalik
val masukan: String? = "Dewi"
if (masukan == null) {
println("Masukan null")
} else {
// masukan otomatis non-null pada else
println("Panjang masukan: ${masukan.length}")
}
// when expression dengan analisis cabang
val data: Any = 3.14
val info: String = when (data) {
is Int -> "Integer dengan nilai $data, kuadrat ${data * data}"
is Double -> "Double dengan nilai $data, dibulatkan ${data.toInt()}"
is String -> "String panjang ${data.length}"
else -> "Tipe lain"
}
println(info)
// Compiler melacak assignment: tipe menyempit setelah assignment
var umur: Any = "dua puluh"
if (umur is String) {
println("Umur dalam teks: ${umur.uppercase()}")
}
umur = 20
if (umur is Int) {
println("Umur dalam angka: ${umur + 5} tahun")
}
// Batasan smart cast pada var yang bisa berubah paralel
var suhu: Any? = 36
if (suhu != null) {
val salin = suhu
println("Suhu: $salin (disalin ke val lokal untuk keamanan)")
}
// Analisis tipe nullable pada kondisi majemuk
val alamat: String? = "Jl. Mawar"
if (alamat != null && alamat.length > 5) {
println("Alamat valid: $alamat")
}
}Output yang Dihasilkan:
Panjang nama: 4
Huruf besar: BUDI
Kuadrat: 1764
Panjang masukan: 4
Double dengan nilai 3.14, dibulatkan 3
Umur dalam teks: DUA PULUH
Umur dalam angka: 25 tahun
Suhu: 36 (disalin ke val lokal untuk keamanan)
Alamat valid: Jl. MawarMari kita bedah setiap bagian penting:
if (nama != null) { println(nama.length) }: setelah null check, kompilator memperlakukannamasebagaiStringnon-nullable di dalam blok. Pemanggilan.lengthdan.uppercase()langsung diizinkan tanpa?.atau!!.if (nilai is Int) { val kuadrat: Int = nilai * nilai }: setelahis Intcocok, kompilator menyempitkan tipe statisnilaidariAnymenjadiInt. Pemanggilannilai * nilaimenghasilkanInttanpa cast eksplisit.if (masukan == null) ... else { println(masukan.length) }: kompilator tahu bahwa pada cabangelse,masukanpasti non-null. Smart cast berlaku pada kedua cabang dengan kondisi yang berlawanan.when (data) { is Int -> "Integer ... ${data * data}" ... }: smart cast juga bekerja pada cabangwhen. Di setiap cabangis T,datadiperlakukan sebagaiT.- Tracking assignment:
var umur: Any = "dua puluh"membuatumurbertipe statisAny. Setelah assignmentumur = 20, kompilator tidak mengubah tipe statis, tetapiif (umur is Int)tetap memicu smart cast. - Salin ke
vallokal untuk keamanan:varyang dapat diubah oleh kode lain (mis. dari thread paralel) tidak boleh diandalkan untuk smart cast. Salin kevallokal terlebih dahulu agar kompilator yakin nilainya stabil. if (alamat != null && alamat.length > 5): operator&&dievaluasi kiri ke kanan. Setelahalamat != nulltrue, kompilator tahualamatnon-nullable untuk sisa kondisi, sehinggaalamat.lengthdiizinkan.
Pelacakan Kondisi Berlawanan #
Kompilator tidak hanya melihat cabang if positif — ia juga melacak konsekuensi pada cabang else. Bila if (x == null) true, maka pada else, x pasti non-null. Analisis ini berlaku untuk is/!is, ==/!=, dan kombinasi dengan && serta ||.
Pelacakan Assignment #
Kompilator melacak perubahan tipe runtime akibat assignment. Bila var x: Any = "..." lalu x = 42, kompilator tetap memperlakukan x sebagai Any secara statis, tetapi is Int check pada titik setelahnya akan memicu smart cast.
Batasan Smart Cast pada var #
Smart cast pada var (terutama yang dapat diakses dari luar atau dari thread lain) tidak selalu bisa diandalkan. Bila properti bisa berubah di antara pengecekan dan penggunaan, kompilator menolak smart cast untuk menghindari kondisi race.
Kondisi Majemuk dengan && #
Pada if (a != null && a.length > 5), kompilator tahu bahwa pada bagian setelah &&, a pasti non-null karena && bersifat short-circuit. Bila a null, bagian kanan && tidak dievaluasi.
Referensi Lanjutan #
- Smart casts — konteks smart cast sebagai bagian dari control flow analysis
- Control flow — panduan resmi control flow yang menjadi dasar smart cast
Ringkasan Mekanisme Control Flow Analysis #
| Trigger | Efek Smart Cast |
|---|---|
if (x != null) { ... } | x non-nullable di dalam blok |
if (x == null) ... else { ... } | x non-nullable di cabang else |
if (x is T) { ... } | x bertipe T di dalam blok |
when (x) { is T -> ... } | x bertipe T di cabang when |
if (x != null && x.f()) | x non-nullable pada sisi kanan && |
Control flow analysis adalah pondasi yang membuat Kotlin tetap ringkas sekaligus aman. Dengan memahami cara kompilator melacak kondisi dan assignment, kita dapat menulis kode yang memanfaatkan smart cast secara penuh tanpa terjebak pada cast eksplisit yang berisiko. Inilah penutup section Struktur Kontrol — pada section berikutnya kita akan beralih ke struktur data dan koleksi Kotlin.