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.
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.
// 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:
=== 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: 10Mari kita telaah bagian-bagian penting dari kode di atas:
bacaDenganFinallyManual:BufferedReaderdibuka sebelumtry, dipakai di dalamtry, lalu ditutup difinally. Blokfinallymenjaminreader.close()dipanggil meskipunreadText()melemparIOException. Tanpafinally, exception akan membuatclose()terlewat dan menyebabkan resource leak.bacaDenganUse: Extension functionuse {}padaCloseablemembungkus polatry-finally-close()secara otomatis. Kode lebih ringkas dan tidak ada risiko lupa memanggilclose().demoFinallySaatException: Membuktikan urutan eksekusi:trymelempar,catchmenangkap,finallyjalan terakhir sebelum function mengembalikan nilai daricatch.demoFinallySaatReturn: Membuktikan bahwafinallyjalan setelah statementreturn 10dievaluasi tetapi sebelum nilai tersebut meninggalkan function. Output menunjukkan baris[finally] ...muncul sebelumNilai kembalian: 10.main: Menjalankan keempat demonstrasi secara berurutan dan menampilkan hasilnya, memudahkan kita melihat kontras antara pola manual danuse {}.
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.
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.
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
finallyuntuk 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.