Menangkap exception baru menyelesaikan separuh masalah: bagaimana dengan pekerjaan rumah yang harus berjalan apa pun yang terjadi? Berkas yang sudah dibuka harus ditutup, koneksi harus dilepas, dan status sementara harus dipulihkan — baik ketika blok try selesai mulus, maupun ketika exception sedang melewati method tersebut. Konstruksi finally hadir untuk kebutuhan itu: blok yang dijamin dieksekusi pada semua jalur keluar dari try. Pada Chapter ini kita buktikan jaminan tersebut lewat berbagai skenario, lalu kita lihat pola pembersihan klasik — beserta satu jebakan lama yang wajib dihindari.
Blok finally Selalu Berjalan #
Blok finally ditulis setelah blok catch terakhir (atau langsung setelah try tanpa catch). JVM menjalankan blok tersebut pada setiap jalur keluar dari try: eksekusi normal, exception yang tertangkap, exception yang tidak tertangkap dan terus merambat, sampai statement return dari dalam try. Program berikut menguji keempat jalur tersebut sekaligus:
public class FinallySelaluJalan {
static void uji(String skenario) {
System.out.println("--- " + skenario);
try {
switch (skenario) {
case "normal" -> System.out.println("try selesai tanpa kegagalan");
case "tertangkap" -> throw new NumberFormatException("abc");
case "kembali" -> {
System.out.println("try melakukan return");
return;
}
default -> throw new IllegalStateException("tidak ditangkap di sini");
}
} catch (NumberFormatException e) {
System.out.println("catch menangani: " + e.getMessage());
} finally {
System.out.println("finally berjalan");
}
System.out.println("setelah try-catch-finally");
}
public static void main(String[] args) {
uji("normal");
uji("tertangkap");
uji("kembali");
try {
uji("lepas");
} catch (IllegalStateException e) {
System.out.println("main menangkap: " + e.getMessage());
}
}
}Output yang Dihasilkan:
--- normal
try selesai tanpa kegagalan
finally berjalan
setelah try-catch-finally
--- tertangkap
catch menangani: abc
finally berjalan
setelah try-catch-finally
--- kembali
try melakukan return
finally berjalan
--- lepas
finally berjalan
main menangkap: tidak ditangkap di siniBaris finally berjalan muncul di keempat skenario — itulah jaminan intinya. Mari kita bedah per skenario:
- Skenario
normal: bloktrytuntas tanpa kegagalan,finallyberjalan, lalu statement setelah konstruksi dieksekusi. - Skenario
tertangkap:NumberFormatExceptiontertangani olehcatch,finallytetap berjalan setelahcatchselesai, dan eksekusi lanjut normal. - Skenario
kembali: statementreturndi dalamtryhendak keluar dari method, tetapi JVM menahan keluar tersebut —finallydieksekusi lebih dulu, baru method benar-benar kembali. Perhatikan bahwa barissetelah try-catch-finallytidak tercetak karenareturnsudah menutup method. - Skenario
lepas:IllegalStateExceptiontidak tertangkap olehcatchyang hanya menerimaNumberFormatException. Exception tersebut keluar dari methoduji— danfinallytetap dieksekusi sebelum keluar terjadi. Barissetelah try-catch-finallyjuga tidak tercetak di skenario ini, danmain-lahyang akhirnya menangkap exception tersebut.
Pola Pembersihan Klasik #
Penggunaan finally paling klasik adalah melepas resource secara manual. Variabel sumber dideklarasikan sebelum try, sumber dipakai di dalam try, dan penutupannya dilakukan di finally sehingga berjalan baik saat sukses maupun saat gagal:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class BersihkanSumber {
static void bacaData() throws IOException {
BufferedReader pembaca = new BufferedReader(new StringReader("satu\ndua"));
try {
System.out.println("baris 1: " + pembaca.readLine());
System.out.println("baris 2: " + pembaca.readLine());
} finally {
System.out.println("menutup pembaca");
pembaca.close();
}
}
public static void main(String[] args) throws IOException {
bacaData();
}
}Output yang Dihasilkan:
baris 1: satu
baris 2: dua
menutup pembacaUrutan keluarannya: dua baris data dari dalam try, lalu menutup pembaca dari finally. Jika readLine() melempar IOException, penutupan tetap terjadi sebelum exception keluar dari method — itulah gunanya pola ini. Tetapi pola manual punya beban yang terasa: variabel harus dideklarasikan di luar try agar terlihat di finally, close() sendiri melempar IOException yang harus dinyatakan, dan bila jumlah sumber bertambah, kode pembersihan menumpuk. Chapter try-with-resources memperkenalkan bentuk ringkas yang menghapus seluruh beban tersebut.
Bahaya return di Dalam finally #
Blok finally yang mengandung return memiliki kekuatan yang berbahaya: statement tersebut menggantikan exception yang sedang merambat. Sinyal kegagalan yang seharusnya sampai ke pemanggil berakhir begitu saja, seolah tidak pernah ada:
public class FinallyMenelanReturn {
static int bacaSaldo() {
try {
throw new IllegalStateException("sinyal kegagalan penting");
} finally {
return 0; // statement ini menelan exception yang sedang terbang
}
}
public static void main(String[] args) {
System.out.println("saldo: " + bacaSaldo());
System.out.println("program selesai tanpa jejak kegagalan");
}
}Output yang Dihasilkan:
saldo: 0
program selesai tanpa jejak kegagalanMethod bacaSaldo() melempar IllegalStateException di baris pertama try — tetapi keluaran program hanya menampilkan saldo: 0 tanpa jejak kegagalan apa pun. Statement return 0 di dalam finally memutus jalur keluar method dan membuang exception yang sedang terbang; pemanggil menerima nilai 0 dan tidak pernah tahu bahwa pembacaan saldo gagal. Bayangkan nilai 0 itu mengalir ke perhitungan pajak. Perilaku serupa juga terjadi bila finally melempar exception baru: exception lama tertimpa dan hilang dari catatan.
Aturan praktisnya ringkas: finally untuk membersihkan, catch untuk menanggapi, dan keduanya tidak boleh mengubah sinyal kegagalan yang sedang berjalan. Dengan disiplin itu, kita siap melangkah ke bentuk pembersihan modern — try-with-resources — yang memanggil close() secara otomatis dan berurutan tanpa satu baris pun di finally.
Referensi #
- Oracle: The finally Block di dev.java
- Class Throwable pada API java.lang
- Java Language Specification bab 11: Exceptions
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
BersihkanSumber.java
Unduh berkasimport java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class BersihkanSumber {
static void bacaData() throws IOException {
BufferedReader pembaca = new BufferedReader(new StringReader("satu\ndua"));
try {
System.out.println("baris 1: " + pembaca.readLine());
System.out.println("baris 2: " + pembaca.readLine());
} finally {
System.out.println("menutup pembaca");
pembaca.close();
}
}
public static void main(String[] args) throws IOException {
bacaData();
}
}
FinallyMenelanReturn.java
Unduh berkaspublic class FinallyMenelanReturn {
static int bacaSaldo() {
try {
throw new IllegalStateException("sinyal kegagalan penting");
} finally {
return 0; // statement ini menelan exception yang sedang terbang
}
}
public static void main(String[] args) {
System.out.println("saldo: " + bacaSaldo());
System.out.println("program selesai tanpa jejak kegagalan");
}
}
FinallySelaluJalan.java
Unduh berkaspublic class FinallySelaluJalan {
static void uji(String skenario) {
System.out.println("--- " + skenario);
try {
switch (skenario) {
case "normal" -> System.out.println("try selesai tanpa kegagalan");
case "tertangkap" -> throw new NumberFormatException("abc");
case "kembali" -> {
System.out.println("try melakukan return");
return;
}
default -> throw new IllegalStateException("tidak ditangkap di sini");
}
} catch (NumberFormatException e) {
System.out.println("catch menangani: " + e.getMessage());
} finally {
System.out.println("finally berjalan");
}
System.out.println("setelah try-catch-finally");
}
public static void main(String[] args) {
uji("normal");
uji("tertangkap");
uji("kembali");
try {
uji("lepas");
} catch (IllegalStateException e) {
System.out.println("main menangkap: " + e.getMessage());
}
}
}