Di banyak bahasa, blok try-catch adalah pernyataan (statement) yang tidak menghasilkan nilai. Kotlin memperlakukan try sebagai expression, sehingga kita bisa langsung mengikat hasilnya ke variabel. Pemulihan dari kegagalan pun jadi ringkas: nilai sukses dan nilai cadangan ditulis dalam satu ekspresi tanpa variabel sementara. Pada chapter ini kita akan menulis try sebagai expression, memadukannya dengan elvis operator, dan membuktikan bahwa blok finally tidak ikut menentukan nilai balik.

try sebagai Expression #

Bentuk paling ringkas dari try sebagai expression terlihat saat kita mengikat hasilnya langsung ke val. Bagian try menghasilkan nilai bila tidak ada exception; bagian catch menghasilkan nilai bila exception tertangkap. Tipe data akhir adalah common supertype dari semua cabang.

kotlin try expression dasar
fun parseAngka(teks: String): Int {
    val angka: Int = try {
        teks.toInt()
    } catch (e: NumberFormatException) {
        0
    }
    return angka
}

Tanpa sifat expression, kode di atas akan membutuhkan variabel var angka: Int = 0 yang diisi pada try lalu ditimpa pada catch. Sifat expression menyingkirkan mutasi tersebut dan membuat niat kode jelas pada satu tempat.

Mengikat Hasil ke Variabel #

Contoh berikut menunjukkan try sebagai expression di beberapa bentuk sekaligus. Kita akan mengikat hasilnya ke val, memadukan dengan elvis operator, dan menyusun try di dalam when.

kotlin
// Demo try sebagai expression: mengembalikan nilai, finally tidak mengubah nilai balik.

fun parseAngka(teks: String): Int {
    val angka: Int = try {
        teks.toInt()
    } catch (e: NumberFormatException) {
        0
    }
    return angka
}

fun parseDenganElvis(teks: String?): Int {
    // try-expression dipadukan dengan elvis operator
    val angka: Int? = try {
        teks?.toInt()
    } catch (e: NumberFormatException) {
        null
    }
    return angka ?: -1
}

fun konfigurasiSumber(sumber: String): String {
    // try-expression di dalam when-expression
    return when (sumber) {
        "env" -> try { bacaDariEnv() } catch (e: IllegalStateException) { "default-env" }
        "file" -> try { bacaDariFile() } catch (e: IllegalStateException) { "default-file" }
        else -> "tidak dikenal"
    }
}

fun bacaDariEnv(): String {
    // simulasi: lempar bila environment variable kosong
    throw IllegalStateException("ENV belum diatur")
}

fun bacaDariFile(): String {
    return "nilai-dari-file"
}

fun demoFinallyTidakMengubahNilai(): Int {
    // finally jalan setelah try, namun nilai balik try tidak berubah.
    val hasil = try {
        42
    } catch (e: Exception) {
        -1
    } finally {
        // statement di sini hanya efek samping, tidak mengubah `hasil`
        println("[finally] jalan setelah try-expression, hasil tetap")
    }
    return hasil
}

fun main() {
    println("=== parseAngka ===")
    for (t in listOf("3", "99", "abc", "")) {
        println("parseAngka(\"$t\") = ${parseAngka(t)}")
    }

    println()
    println("=== parseDenganElvis ===")
    for (t in listOf("7", "xyz", null)) {
        println("parseDenganElvis(\"$t\") = ${parseDenganElvis(t)}")
    }

    println()
    println("=== try-expression di dalam when ===")
    for (sumber in listOf("env", "file", "cache")) {
        println("sumber=$sumber -> ${konfigurasiSumber(sumber)}")
    }

    println()
    println("=== finally tidak mengubah nilai try-expression ===")
    val x = demoFinallyTidakMengubahNilai()
    println("Nilai yang dikembalikan = $x")
}

Output yang Dihasilkan:

text
=== parseAngka ===
parseAngka("3") = 3
parseAngka("99") = 99
parseAngka("abc") = 0
parseAngka("") = 0

=== parseDenganElvis ===
parseDenganElvis("7") = 7
parseDenganElvis("xyz") = -1
parseDenganElvis("null") = -1

=== try-expression di dalam when ===
sumber=env -> default-env
sumber=file -> nilai-dari-file
sumber=cache -> tidak dikenal

=== finally tidak mengubah nilai try-expression ===
[finally] jalan setelah try-expression, hasil tetap
Nilai yang dikembalikan = 42

Mari kita bedah baris-baris penting dari kode di atas:

  1. parseAngka: val angka: Int = try { ... } catch { ... }. Cabang try menghasilkan Int dari teks.toInt(); cabang catch menghasilkan Int literal 0. Karena kedua cabang bertipe Int, variabel angka pun bertipe Int.
  2. parseDenganElvis: Cabang catch menghasilkan null, sehingga tipe try secara keseluruhan menjadi Int?. Elvis operator ?: kemudian mengganti null dengan -1, menghasilkan Int non-null.
  3. konfigurasiSumber: try-expression dipakai sebagai cabang when. Setiap cabang memilih strategi pemulihan yang berbeda ("default-env" vs "default-file"), menunjukkan keterbacaan kode yang dipersingkat oleh sifat expression.
  4. demoFinallyTidakMengubahNilai: Blok finally hanya mencetak pesan. Nilai balik function tetap 42 (dari cabang try) dan tidak diubah oleh finally.
  5. main: Loop for memakai string template $t untuk menampilkan input dan hasil. Pendekatan ini memudahkan pemeriksaan visual saat membandingkan banyak kasus.

Tipe Hasil dan Common Supertype #

Bila cabang try dan catch menghasilkan tipe berbeda, kompilator akan memilih common supertype. Misalnya try menghasilkan String dan catch menghasilkan Int, maka tipe hasil try-expression adalah Any. Hal ini sering kali tidak diinginkan karena kita kehilangan informasi tipe.

kotlin Tipe gabungan yang longgar
fun ambilNilai(teks: String): Any {
    return try {
        teks.toInt()
    } catch (e: NumberFormatException) {
        "bukan angka"
    }
}

Pada contoh di atas, tipe hasil adalah Any (gabungan Int dan String). Pemanggil harus melakukan pengecekan tipe untuk memproses hasil lebih lanjut. Lebih baik menyamakan tipe kedua cabang, misalnya dengan membungkus hasil ke dalam sealed class Hasil atau dengan Result<Int> (lihat chapter Result Type Patterns).

finally Tidak Memengaruhi Nilai Balik #

Satu hal yang sering membingungkan: finally tidak berkontribusi pada nilai try-expression. Bahkan bila finally berisi statement return, nilainya tidak menjadi hasil try — return di finally justru akan menelan exception yang sedang naik dan menggantikan nilai balik function, sebuah kejadian yang biasanya tidak diinginkan.

kotlin finally tidak mengubah hasil
fun hitungAman(a: Int, b: Int): Int {
    val hasil = try {
        a / b
    } catch (e: ArithmeticException) {
        0
    } finally {
        println("operasi $a / $b selesai")
    }
    return hasil * 2
}

Pada function hitungAman, finally hanya mencatat log. Nilai hasil ditentukan sepenuhnya oleh cabang try atau catch. Pemisahan tanggung jawab ini membuat try-expression dapat dipakai sebagai ekspresi murni sementara finally hanya menjalankan efek samping.

Referensi Lanjutan #

  • Exceptions — bagian try sebagai expression yang menghasilkan nilai

Penutup #

Dengan try sebagai expression, kode pemulihan kegagalan menjadi ringkas dan ekspresif: hasil sukses dan hasil cadangan ditulis pada satu tempat. Pemadukan dengan elvis operator dan when membuat pola seperti parse-or-default terbaca alami. Berikutnya, kita akan mendalami blok finally secara khusus, termasuk pola use {} untuk pembersihan resource otomatis.