Pada kebanyakan bahasa, throw adalah pernyataan biasa yang berhenti mengeksekusi blok saat ini. Kotlin melangkah lebih jauh: throw adalah expression bertipe Nothing. Tipe ini memiliki peran khusus dalam sistem tipe Kotlin — ia menandakan ekspresi yang tidak pernah menghasilkan nilai karena aliran kontrol berhenti di titik tersebut. Pada chapter ini kita akan memanfaatkan sifat expression throw pada elvis operator, cabang when, dan function dengan return type non-void.
throw sebagai Expression #
Karena throw bertipe Nothing, ia dapat muncul di tempat-tempat yang mengharapkan nilai. Tipe Nothing adalah subtype dari semua tipe lain di Kotlin, sehingga kompilator mengizinkan throw muncul di cabang when, di sisi kanan elvis, atau di akhir function yang seharusnya mengembalikan Int, String, atau tipe apa pun.
fun namaWajib(maybe: String?): String {
val nama: String = maybe ?: throw IllegalStateException("nama wajib ada")
return nama.uppercase()
}Tanpa sifat expression, kita perlu menulis if (maybe == null) throw ... else return maybe.uppercase(). Sifat expression throw membuat kode pemulihan nilai null menjadi satu baris yang ringkas.
Memadukan throw dengan Elvis #
Elvis operator ?: mengembalikan operand kiri bila non-null, dan operand kanan bila kiri null. Bila operand kanan adalah throw, kompilator mengenali bahwa kasus null akan berhenti di titik tersebut, sehingga tipe hasil keseluruhan tetap non-null.
// Demo throw sebagai expression bertipe Nothing, dipakai dalam elvis dan when.
fun cariPengguna(id: Int): String {
if (id <= 0) {
// throw adalah expression bertipe Nothing; cocok di cabang when yang butuh tipe sama
throw IllegalArgumentException("id harus positif, diterima: $id")
}
return "Pengguna-$id"
}
fun namaAtauGagal(maybe: String?): String {
// elvis dengan throw: bila maybe null, lempar exception
val nama: String = maybe ?: throw IllegalStateException("nama wajib ada")
return nama.uppercase()
}
fun bagiAman(a: Int, b: Int): Int {
// bila b == 0, gunakan throw sebagai salah satu cabang when
return when {
b == 0 -> throw ArithmeticException("pembagi tidak boleh nol")
else -> a / b
}
}
fun validasiUmur(umur: Int): String {
require(umur >= 0) { "umur tidak boleh negatif: $umur" }
require(umur <= 150) { "umur di luar batas wajar: $umur" }
return when {
umur < 12 -> "anak"
umur < 18 -> "remaja"
umur < 60 -> "dewasa"
else -> "lansia"
}
}
fun prosesPerintah(perintah: String): String {
// throw di cabang else when: kompilator tahu semua cabang menghasilkan String
return when (perintah) {
"halo" -> "Halo juga!"
"waktu" -> "2026-01-15"
else -> throw UnsupportedOperationException("perintah tidak dikenal: $perintah")
}
}
fun tidakPernahKembali(): Int {
// fungsi dengan return type Int, namun throw menjamin tidak pernah benar-benar return
throw IllegalStateException("fungsi ini selalu melempar")
}
fun main() {
println("=== cariPengguna ===")
println(cariPengguna(7))
println()
println("=== namaAtauGagal ===")
println(namaAtauGagal("Budi"))
// namaAtauGagal(null) // uncomment untuk melihat IllegalStateException
println()
println("=== bagiAman ===")
println("10 / 2 = ${bagiAman(10, 2)}")
try {
bagiAman(5, 0)
} catch (e: ArithmeticException) {
println("tangkap: ${e.message}")
}
println()
println("=== validasiUmur dengan require ===")
for (u in listOf(8, 17, 40, 70)) {
println("umur $u -> ${validasiUmur(u)}")
}
try {
validasiUmur(-1)
} catch (e: IllegalArgumentException) {
println("tangkap: ${e.message}")
}
println()
println("=== prosesPerintah dengan throw di else ===")
println(prosesPerintah("halo"))
println(prosesPerintah("waktu"))
try {
prosesPerintah("hapus")
} catch (e: UnsupportedOperationException) {
println("tangkap: ${e.message}")
}
}Output yang Dihasilkan:
=== cariPengguna ===
Pengguna-7
=== namaAtauGagal ===
BUDI
=== bagiAman ===
10 / 2 = 5
tangkap: pembagi tidak boleh nol
=== validasiUmur dengan require ===
umur 8 -> anak
umur 17 -> remaja
umur 40 -> dewasa
umur 70 -> lansia
tangkap: umur tidak boleh negatif: -1
=== prosesPerintah dengan throw di else ===
Halo juga!
2026-01-15
tangkap: perintah tidak dikenal: hapusMari kita perhatikan bagian-bagian penting dari kode di atas:
cariPengguna: Pemeriksaanif (id <= 0) throw ...diletakkan sebelumreturn. KarenathrowbertipeNothing, kompilator mengizinkanreturn "Pengguna-$id"setelahnya, dengan pemahaman bahwa cabangthrowtidak akan pernah mencapaireturn.namaAtauGagal: Elvis?: throwmembuatnamabertipeStringnon-null. Bilamaybenull, throw dieksekusi dan function berhenti; bila tidak,namadiisi nilai non-null.bagiAman: Cabangb == 0 -> throw ArithmeticException(...)dan cabangelse -> a / bsama-sama menghasilkanInt(cabangthrowdianggap menghasilkanNothing, yang merupakan subtype dariInt).validasiUmur: Function standard libraryrequire(condisi) { pesan }melemparIllegalArgumentExceptionbila kondisi false. Bentuk ringkas ini menggantikan pola manualif (!kondisi) throw ....prosesPerintah: Cabangelse -> throw UnsupportedOperationException(...)menjaminwhenmenghasilkanString(kompilator menganggapNothingkompatibel). Function mengembalikanStringtanpa perlu operator!!atau fallback eksplisit.
Tipe Nothing dan Implikasinya #
Nothing adalah tipe khusus yang tidak memiliki instance. Function yang return type-nya Nothing dijamin tidak pernah kembali secara normal — entah melempar exception atau mengulang selamanya (misalnya loop tak hingga). Karena Nothing adalah subtype dari setiap tipe lain, sebuah Nothing dapat dipakai sebagai pengganti tipe apa pun dalam konteks yang mengharapkan nilai.
fun gagal(pesan: String): Nothing {
throw IllegalStateException(pesan)
}
fun cari(id: Int): String {
if (id <= 0) return gagal("id tidak valid") // OK: Nothing kompatibel dengan String
return "Pengguna-$id"
}Pada contoh di atas, function gagal mendeklarasikan return type Nothing. Saat dipanggil di cari, kompilator mengizinkan return gagal(...) seolah-olah gagal mengembalikan String. Hal ini dimungkinkan karena Nothing adalah subtype universal.
Helper require dan check #
Standard library Kotlin menyediakan dua helper yang membungkus pola if (!kondisi) throw:
fun setHarga(harga: Int) {
require(harga >= 0) { "harga tidak boleh negatif: $harga" } // IllegalArgumentException
check(harga < 1_000_000) { "harga di atas batas wajar" } // IllegalStateException
}
fun main() {
try { setHarga(-50) } catch (e: IllegalArgumentException) { println(e.message) }
try { setHarga(2_000_000) } catch (e: IllegalStateException) { println(e.message) }
}Helper require dipakai untuk validasi argumen input (melempar IllegalArgumentException), sementara check dipakai untuk memverifikasi state internal (melempar IllegalStateException). Keduanya menerima lambda untuk pesan agar evaluasi pesan hanya terjadi bila kondisi false.
Referensi Lanjutan #
- Exceptions
— bagian
throwsebagai expression yang menghasilkanNothing
Penutup #
Karena throw adalah expression bertipe Nothing, ia dapat diandalkan untuk mempersingkat kode pemulihan nilai null, validasi argumen, dan cabang when yang tidak mungkin terjadi. Helper require dan check melengkapi pola ini untuk validasi argumen dan state. Berikutnya, kita akan mendefinisikan jenis exception kita sendiri melalui custom exception yang mewarisi Exception atau RuntimeException.