Pada tipe Any atau Any?, kita sering perlu mengonversi ke tipe yang lebih spesifik. Operator cast as di Kotlin bersifat unsafe — bila gagal, ia melempar ClassCastException. Untuk menangani tipe yang tidak pasti secara aman, Kotlin menyediakan operator safe cast as? yang mengembalikan null saat konversi tidak memungkinkan. Pada chapter ini kita akan membedah cara kerja as?, perbedaannya dengan as, serta pola umum kombinasi as? dengan operator Elvis dan mapNotNull.
Perbedaan as dan as? #
| Operator | Nama | Saat Gagal | Tipe Kembalian | Cocok untuk |
|---|---|---|---|---|
as | Unsafe cast | Melempar ClassCastException | T (non-nullable) | Tipe sudah pasti benar |
as? | Safe cast | Mengembalikan null | T? (nullable) | Tipe mungkin tidak cocok |
Operator as sebaiknya dipakai hanya bila kompilator membuktikan tipe target pasti benar, atau bila kita ingin program gagal cepat (fail-fast) saat asumsi dilanggar. Operator as? lebih disukai saat data berasal dari sumber yang tidak terkontrol seperti JSON, database, atau library Java.
Cara Kerja as? #
Operator as? menguji apakah nilai dapat dikonversi ke tipe target. Bila ya, hasilnya nilai tersebut dengan tipe target. Bila tidak, hasilnya null bertipe nullable. Operator ini tidak pernah melempar exception karena penanganan ketidakcocokan ditangani via nilai null.
fun main() {
val objek1: Any = "Halo, Kotlin"
val teks1: String? = objek1 as? String
println("Hasil as? String: $teks1, panjang=${teks1?.length}")
val objek2: Any = 42
val teks2: String? = objek2 as? String
println("Hasil 42 as? String: $teks2")
val objek3: Any = listOf(1, 2, 3)
val daftar: List<*>? = objek3 as? List<*>
println("Hasil as? List: $daftar, ukuran=${daftar?.size}")
val objek4: Any = mapOf("kota" to "Bandung")
val peta: Map<*, *>? = objek4 as? Map<*, *>
println("Hasil as? Map: $peta")
val angka: Any = 100L
val sebagaiInt: Int? = angka as? Int
val sebagaiLong: Long? = angka as? Long
println("100L as? Int: $sebagaiInt")
println("100L as? Long: $sebagaiLong")
val daftarCampuran: List<Any> = listOf("teks", 42, 3.14, true)
val hanyaString: List<String> = daftarCampuran.mapNotNull { it as? String }
println("Hanya string: $hanyaString")
val hanyaAngka: List<Int> = daftarCampuran.mapNotNull { it as? Int }
println("Hanya angka Int: $hanyaAngka")
val tipeTidakDiketahui: Any = "Budi"
val panjangNama: Int? = (tipeTidakDiketahui as? String)?.length
println("Panjang nama: $panjangNama")
val input: Any = 12345
val hasil: String = (input as? String) ?: "<bukan string>"
println("Hasil dengan elvis: $hasil")
val nullEksplisit: Any? = null
val ujiNull: String? = nullEksplisit as? String
println("null as? String: $ujiNull")
try {
val salah: String = objek2 as String
println(salah)
} catch (e: ClassCastException) {
println("ClassCastException: ${e.message?.take(60)}")
}
}Output yang Dihasilkan:
Hasil as? String: Halo, Kotlin, panjang=12
Hasil 42 as? String: null
Hasil as? List: [1, 2, 3], ukuran=3
Hasil as? Map: {kota=Bandung}
100L as? Int: null
100L as? Long: 100
Hanya string: [teks]
Hanya angka Int: [42]
Panjang nama: 4
Hasil dengan elvis: <bukan string>
null as? String: null
ClassCastException: class java.lang.Integer cannot be cast to class java.lang.StMari kita uraikan baris-baris penting pada contoh di atas:
- Baris 3 (
objek1 as? String):objek1berisi"Halo, Kotlin"yang merupakanString. Cast berhasil danteks1bertipeString?berisi nilai tersebut. - Baris 7 (
objek2 as? String):objek2berisi42(Int), bukanString. Cast gagal secara diam-diam, hasilnyanull. Tidak ada exception yang dilempar. - Baris 10 (
objek3 as? List<*>): safe cast ke tipe generic.List<*>(star projection) menyatakan tipe elemen tidak diketahui, namun struktur list itu sendiri tetap dapat dipakai. - Baris 17-19 (
angka as? Intvsangka as? Long): tipe angka tidak saling kompatibel meskipun secara numerik bernilai sama.100LadalahLong, sehinggaas? Intmenghasilkannull. Bila ingin konversi numerik, gunakan methodtoInt()atautoLong(), bukan cast. - Baris 22 (
daftarCampuran.mapNotNull { it as? String }): pola paling sering dipakai bersamaas?.mapNotNullmenerapkan lambda ke setiap elemen dan menyaringnull. Hasilnya list hanya berisi elemen yang berhasil di-cast. - Baris 28 (
(tipeTidakDiketahui as? String)?.length): kombinasi safe cast dengan safe call. Bila cast gagal, hasilnyanull; bila cast berhasil,.lengthdipanggil. Rantai ini sangat umum saat memproses data dinamis. - Baris 32 (
(input as? String) ?: "<bukan string>"): kombinasi safe cast dengan operator Elvis. Hasil akhir selalu non-nullableString, dengan nilai pengganti yang jelas bila cast gagal. - Baris 36 (
nullEksplisit as? String): bila nilai aslinyanull, safe cast mengembalikannullalih-alih melemparNullPointerException— perilaku yang konsisten dengan filosofi null-safety Kotlin. - Baris 39-43 (
objek2 as Stringdalamtry-catch): sebagai pembanding, unsafe castaslangsung melemparClassCastExceptionsaat gagal. Inilah alasanas?lebih disukai kecuali kita yakin tipe benar-benar cocok.
Referensi Lanjutan #
- Smart casts
— bagian safe cast
as?pada panduan typecasts resmi - Type casts
— panduan resmi unsafe cast
asdan safe castas?
Setelah memahami as?, langkah berikutnya adalah mengenal fitur smart cast yang memungkinkan kompilator menyesuaikan tipe secara otomatis setelah pengecekan is — topik yang akan kita bahas pada chapter Smart Casts.