Banyak operasi penting melibatkan resource eksternal: file, koneksi database, socket jaringan, atau stream I/O. Resource tersebut harus ditutup setelah dipakai, terlepas dari apakah operasi sukses atau melempar exception. Kotlin menyediakan blok finally sebagai tempat pasti untuk pembersihan, dan extension function use {} sebagai pengganti try-with-resources Java. Pada chapter ini kita akan membandingkan kedua pendekatan, melihat kapan finally dijalankan, dan menghindari jebakan return di dalam finally.

Peran finally #

Blok finally adalah bagian opsional dari try. Ia dijalankan selalu, baik saat blok try sukses, saat exception tertangkap, maupun saat exception tidak tertangkap dan akan naik ke pemanggil. Karena sifatnya yang dijamin, finally adalah tempat yang tepat untuk menutup resource, merilis lock, atau mencatat log aktivitas.

kotlin Struktur try-catch-finally
try {
    // kode utama yang berisiko melempar exception
} catch (e: IOException) {
    // penanganan exception
} finally {
    // pembersihan yang dijamin jalan
}

Urutan eksekusi yang penting diingat: try → (jika exception) catch → finally. Bila try atau catch mengandung return, finally tetap dijalankan sebelum nilai return benar-benar meninggalkan function. Hal ini memungkinkan finally melakukan pembersihan terlebih dahulu, lalu nilai return diberikan ke pemanggil.

Pembersihan Resource Manual #

Cara klasik menutup resource adalah dengan try-finally tanpa catch. Resource dibuka di luar try, dipakai di dalam try, dan ditutup di dalam finally. Pola ini dulu umum di Java sebelum try-with-resources diperkenalkan.

kotlin
// Demo finally untuk pembersihan, termasuk pola .use {} sebagai pengganti try-with-resources.

import java.io.BufferedReader
import java.io.StringReader

fun bacaDenganFinallyManual(teks: String): String {
    val reader = BufferedReader(StringReader(teks))
    try {
        // membaca seluruh isi reader sebagai satu string
        return reader.readText()
    } finally {
        // finally dijamin jalan, bahkan saat readText melempar exception
        println("[finally] menutup reader manual")
        reader.close()
    }
}

fun bacaDenganUse(teks: String): String {
    // .use {} memanggil close() di akhir blok secara otomatis, mirip try-with-resources Java.
    return BufferedReader(StringReader(teks)).use { reader ->
        reader.readText()
    }
}

fun demoFinallySaatException(): String {
    try {
        // simulasi kegagalan: akses index di luar batas
        val daftar = listOf(1, 2, 3)
        return "elemen keempat = ${daftar[3]}"
    } catch (e: IndexOutOfBoundsException) {
        println("[catch] tangkap: ${e::class.simpleName}")
        return "gagal"
    } finally {
        println("[finally] jalan baik saat sukses maupun exception")
    }
}

fun demoFinallySaatReturn(): Int {
    try {
        println("[try] sebelum return")
        return 10
    } finally {
        // finally jalan SEBELUM nilai return benar-benar keluar dari function
        println("[finally] jalan setelah return statement, sebelum function benar-benar keluar")
    }
}

fun main() {
    println("=== Baca dengan finally manual ===")
    val isi1 = bacaDenganFinallyManual("baris pertama\nbaris kedua")
    println("Isi terbaca:")
    println(isi1)

    println()
    println("=== Baca dengan .use {} ===")
    val isi2 = bacaDenganUse("data penting")
    println("Isi terbaca: $isi2")

    println()
    println("=== finally saat exception ===")
    val hasil = demoFinallySaatException()
    println("Hasil: $hasil")

    println()
    println("=== finally saat return ===")
    val kembalian = demoFinallySaatReturn()
    println("Nilai kembalian: $kembalian")
}

Output yang Dihasilkan:

text
=== Baca dengan finally manual ===
[finally] menutup reader manual
Isi terbaca:
baris pertama
baris kedua

=== Baca dengan .use {} ===
Isi terbaca: data penting

=== finally saat exception ===
[catch] tangkap: ArrayIndexOutOfBoundsException
[finally] jalan baik saat sukses maupun exception
Hasil: gagal

=== finally saat return ===
[try] sebelum return
[finally] jalan setelah return statement, sebelum function benar-benar keluar
Nilai kembalian: 10

Mari kita telaah bagian-bagian penting dari kode di atas:

  1. bacaDenganFinallyManual: BufferedReader dibuka sebelum try, dipakai di dalam try, lalu ditutup di finally. Blok finally menjamin reader.close() dipanggil meskipun readText() melempar IOException. Tanpa finally, exception akan membuat close() terlewat dan menyebabkan resource leak.
  2. bacaDenganUse: Extension function use {} pada Closeable membungkus pola try-finally-close() secara otomatis. Kode lebih ringkas dan tidak ada risiko lupa memanggil close().
  3. demoFinallySaatException: Membuktikan urutan eksekusi: try melempar, catch menangkap, finally jalan terakhir sebelum function mengembalikan nilai dari catch.
  4. demoFinallySaatReturn: Membuktikan bahwa finally jalan setelah statement return 10 dievaluasi tetapi sebelum nilai tersebut meninggalkan function. Output menunjukkan baris [finally] ... muncul sebelum Nilai kembalian: 10.
  5. main: Menjalankan keempat demonstrasi secara berurutan dan menampilkan hasilnya, memudahkan kita melihat kontras antara pola manual dan use {}.

use sebagai Pengganti try-with-resources #

Java 7 memperkenalkan try-with-resources, yang otomatis memanggil close() pada semua resource yang mendeklarasikan AutoCloseable. Kotlin tidak memiliki sintaks khusus untuk ini; sebagai gantinya, standard library menyediakan extension function use {} pada interface Closeable dan AutoCloseable.

kotlin Bentuk umum .use []
import java.io.FileReader

fun hitungBaris(path: String): Int {
    return FileReader(path).use { reader ->
        reader.readLines().size
    }
}

Extension function use {} membungkus pemanggilan dalam try-finally dan memanggil close() pada akhir blok. Bila blok melempar exception, exception tersebut tetap naik ke pemanggil setelah close() dijalankan. Dengan demikian, use {} memberikan jaminan pembersihan yang sama dengan try-with-resources Java.

finally dan return #

Salah satu jebakan klasik adalah menulis return di dalam finally. Meskipun legal, return di finally akan menelan exception yang sedang naik dan menggantikan nilai balik function. Stack trace asli tidak terlihat oleh pemanggil, dan debugging menjadi sangat sulit.

kotlin finally menelan exception (hindari)
fun salahkan(): Int {
    try {
        throw IllegalStateException("kegagalan penting")
    } finally {
        return 0  // menelan exception, pemanggil tidak tahu ada kegagalan
    }
}

Pada contoh di atas, IllegalStateException seharusnya naik ke pemanggil. Namun return 0 di finally menelannya, dan function mengembalikan 0 seolah-olah tidak ada masalah. Perilaku ini hampir selalu tidak diinginkan. Hindari return di finally; simpan finally hanya untuk pembersihan.

Referensi Lanjutan #

  • Exceptions — penjelasan resni blok finally untuk pembersihan

Penutup #

Blok finally menjamin pembersihan resource selalu terjadi, sementara use {} mengotomatiskan pola tersebut untuk Closeable dan AutoCloseable. Dengan menghindari return di dalam finally, kita menjaga stack trace tetap akurat dan pemulihan kegagalan tetap dapat ditelusuri. Berikutnya, kita akan melihat throw tidak hanya sebagai statement, tetapi juga sebagai expression bertipe Nothing.