Sumber daya adalah objek yang wajib ditutup setelah dipakai, misalnya berkas, koneksi basis data, atau socket, dan kelupaan menutupnya membuat koneksi menggantung hingga program berhenti. Java menyediakan try-with-resources, bentuk try yang menutup setiap sumber daya secara otomatis di akhir blok, apa pun yang terjadi di dalamnya. Syaratnya satu, yaitu kelas sumber daya mengimplementasikan interface AutoCloseable. Topik ini menunjukkan beban penutupan manual yang dihilangkan, sintaksnya, urutan penutupan beberapa sumber daya, serta perilakunya saat penutupan itu sendiri gagal.
Pola penutupan manual dan bebannya#
Sebelum ada try-with-resources, pembersihan dititipkan kepada blok finally, sehingga setiap pemakaian sumber daya menuntut pola yang sama dan panjang. Bentuk seperti ini masih banyak muncul di kode lama, sehingga perlu dikenali dahulu sebelum digantikan.
// tidak-dijalankan
KoneksiPalsu koneksi = new KoneksiPalsu("localhost");
try {
koneksi.kirim("halo");
} finally {
koneksi.close();
}Pola ini benar tetapi rapuh. Variabel sumber daya harus dideklarasikan di luar try, setiap sumber daya tambahan menambah satu tingkat sarang, dan penulis baru mudah lupa menutupnya sama sekali. Ada jebakan yang lebih halus. Bila tubuh try melempar exception dan close() juga melempar, exception dari blok finally yang dijalankan paling akhir akan menimpa exception tubuh, sehingga penyebab utama kegagalan hilang dari laporan. try-with-resources menyelesaikan ketiga persoalan itu dengan satu sintaks.
Sintaks try-with-resources#
try-with-resources menempatkan deklarasi sumber daya di dalam kurung setelah kata kunci try, dan JVM memanggil close()-nya otomatis begitu blok berakhir, baik secara normal maupun karena exception. Di balik layar, kompilator menyusun bentuk ini menjadi try-finally yang lebih rumit, lengkap dengan penanganan bila penutupannya sendiri gagal. Kelas sumber daya cukup mengimplementasikan AutoCloseable, yang hanya menuntut satu metode bernama close. Agar bisa dijalankan di mana saja, contoh berikut memakai kelas buatan sendiri, yaitu KoneksiPalsu yang meniru perilaku koneksi jaringan.
public class DemoSumberDaya {
public static void main(String[] args) {
try (KoneksiPalsu koneksi = new KoneksiPalsu("localhost")) {
koneksi.kirim("halo");
} catch (Exception e) {
System.out.println("Ditangkap: " + e.getMessage());
}
}
}
class KoneksiPalsu implements AutoCloseable {
private final String tujuan;
KoneksiPalsu(String tujuan) {
this.tujuan = tujuan;
System.out.println("Koneksi ke " + tujuan + " dibuka");
}
void kirim(String pesan) {
System.out.println("Terkirim: " + pesan);
}
@Override
public void close() {
System.out.println("Koneksi ke " + tujuan + " ditutup");
}
}Koneksi ke localhost dibuka
Terkirim: halo
Koneksi ke localhost ditutupMenjalankannya dari terminal cukup dengan satu perintah berikut.
# berada di folder tempat DemoSumberDaya.java disimpan
java DemoSumberDaya.javaKoneksi ke localhost dibuka
Terkirim: halo
Koneksi ke localhost ditutupUrutan cetakannya menunjukkan mekanismenya, yaitu konstruktor membuka koneksi, tubuh try mengirim pesan, lalu close() dipanggil tanpa pernyataan apa pun yang memintanya. Bila kirim melempar exception, close() tetap dipanggil lebih dahulu sebelum exception ditangkap oleh blok catch. Kelas pustaka seperti BufferedReader, InputStream, dan Connection mengikuti pola yang sama persis karena semuanya implementasi AutoCloseable, sehingga penulisan new BufferedReader(...) di header try langsung mendapat penutupan otomatis.
Beberapa sumber daya dalam satu pernyataan#
Beberapa sumber daya dideklarasikan di header yang sama dengan pemisah titik koma, dan penutupannya dilakukan dalam urutan terbalik dari deklarasinya. Sumber daya yang dibuka terakhir ditutup lebih dahulu. Urutan ini biasanya dikehendaki karena sumber daya yang dibuka belakangan sering bergantung pada sumber daya yang dibuka lebih awal. Contoh berikut memakai dua kanal data sederhana yang keduanya mengimplementasikan AutoCloseable.
public class DemoBanyakSumberDaya {
public static void main(String[] args) {
try (KanalData a = new KanalData("kanal-A");
KanalData b = new KanalData("kanal-B")) {
a.baca();
b.baca();
}
}
}
class KanalData implements AutoCloseable {
private final String nama;
KanalData(String nama) {
this.nama = nama;
System.out.println(nama + " dibuka");
}
void baca() {
System.out.println(nama + " dibaca");
}
@Override
public void close() {
System.out.println(nama + " ditutup");
}
}kanal-A dibuka
kanal-B dibuka
kanal-A dibaca
kanal-B dibaca
kanal-B ditutup
kanal-A ditutupDua kanal dibuka sesuai urutan deklarasi, dipakai berurutan, lalu ditutup terbalik, yaitu kanal-B lebih dulu daripada kanal-A. Perhatikan bahwa try ini tidak memiliki blok catch sekalipun, dan program tetap sah karena penutupan otomatis sudah terjadi di akhir blok. Susunan yang sama berlaku bila baca melempar exception di tengah jalan, karena kedua kanal tetap ditutup sebelum exception bergerak keluar.
Aturan ini juga mencakup kegagalan saat pembukaan. Bila konstruktor kanal-B melempar exception, kanal-A yang sudah berhasil dibuka tetap ditutup secara otomatis, sehingga tidak ada sumber daya yang tertinggal akibat kegagalan itu. Perilaku ini sulit dipertahankan dengan penulisan manual, karena urutan inisialisasi dan pembersihannya harus dijaga dengan tangan.
Saat penutupan juga gagal#
Metode close() sendiri boleh melempar exception, dan try-with-resources menanganinya secara khusus. Bila tubuh try berhasil tetapi penutupan gagal, exception dari close() menjadi exception utama yang bergerak keluar. Bila keduanya gagal, exception tubuh tetap menjadi yang utama, sedangkan exception penutupan disimpan sebagai exception tertekan (suppressed) dan dapat dibaca lewat getSuppressed().
public class DemoTertekan {
public static void main(String[] args) {
jalankan(false);
System.out.println("--");
jalankan(true);
}
static void jalankan(boolean tubuhGagal) {
try (KanalLabil kanal = new KanalLabil()) {
if (tubuhGagal) {
throw new IllegalStateException("pembacaan data gagal");
}
System.out.println("Pembacaan berhasil");
} catch (Exception e) {
System.out.println("Tertangkap: " + e.getMessage());
for (Throwable t : e.getSuppressed()) {
System.out.println("Tertekan: " + t.getMessage());
}
}
}
}
class KanalLabil implements AutoCloseable {
@Override
public void close() {
throw new IllegalStateException("penutupan kanal gagal");
}
}Pembacaan berhasil
Tertangkap: penutupan kanal gagal
--
Tertangkap: pembacaan data gagal
Tertekan: penutupan kanal gagalKasus pertama menunjukkan penutupan yang gagal sendirian, sehingga exception dari close() menjadi pesan yang tertangkap. Kasus kedua lebih penting, yaitu tubuh gagal dan penutupan gagal, tetapi yang tampil sebagai exception utama adalah kegagalan pembacaan data, bukan kegagalan penutupan. Informasi penutupan tidak hilang, melainkan tersimpan sebagai exception tertekan yang tercantum dalam stack trace dan dapat diperiksa lewat getSuppressed(). Pada stack trace bawaan JVM, exception tertekan muncul di bawah baris berawalan Suppressed, sehingga rantai kegagalan penutupan tetap terbaca tanpa kode tambahan apa pun. Perbandingan dengan pola manual di awal topik menunjukkan keunggulannya, karena pada finally biasa exception penutupan justru menimpa penyebab utama.
Kesalahan umum#
- Menutup sumber daya secara manual di dalam tubuh try-with-resources.
close()kemudian dipanggil dua kali, dan kelas yang tidak idempoten bisa memunculkan error pada pemanggilan kedua. - Mengira penutupan terjadi di akhir metode. Penutupan terjadi di akhir blok try, sehingga memakai sumber daya setelah blok itu berarti memakai objek yang sudah tertutup.
- Menaruh objek biasa ke header try meski kelasnya bukan sumber daya. Tidak ada yang rusak, tetapi pembaca kode tertipu menyangka objek itu perlu ditutup.
- Menganggap
close()selalu berhasil. Kelas sepertiOutputStreamdapat melempar exception saat sisa data diambil, sehingga kegagalan penutupan tetap layak dicatat.
Ringkasan#
- try-with-resources mendeklarasikan objek
AutoCloseabledi header try dan memanggilclose()-nya otomatis di akhir blok, apa pun hasil eksekusinya. - Beberapa sumber daya ditutup dalam urutan terbalik dari deklarasinya.
- Bila tubuh dan penutupan sama-sama gagal, exception tubuh menjadi utama dan exception penutupan tersimpan sebagai exception tertekan yang terbaca lewat
getSuppressed(). - Kelas buatan sendiri ikut pola ini dengan mengimplementasikan satu metode
close.
Referensi#
- The try-with-resources Statement — Oracle — penjelasan sintaks try-with-resources pada tutorial resmi Java.
- AutoCloseable (Java SE 21) — Oracle — kontrak interface AutoCloseable beserta catatan idempotensi close.
- Try-with-resources — JLS SE 21 — spesifikasi resmi perilaku penutupan dan exception tertekan.