Pola pembersihan dengan finally pada Chapter sebelumnya bekerja, tetapi menuntut disiplin manual: deklarasi di luar try, penutupan di finally, dan close() yang sendiri melempar exception. Sejak Java 7, bahasa menyediakan bentuk yang jauh lebih ringkas: try-with-resources — resource dideklarasikan langsung di kurung setelah kata kunci try, dan compiler menyisipkan penutupan otomatis pada semua jalur keluar. Pada Chapter ini kita pelajari syarat AutoCloseable, urutan penutupan, dan perilakunya saat badan try melempar exception.

Bentuk dan Syarat AutoCloseable #

Bentuk umumnya: try (Deklarasi resource) { ... }. Setiap variabel di dalam kurung disebut resource dan tipenya harus mengimplementasikan interface AutoCloseable — interface dengan satu method, close(). Saat blok try berakhir, JVM memanggil close() tiap resource otomatis, dalam urutan kebalikan dari urutan deklarasinya. Mari kita buktikan dengan class buatan sendiri:

class SumberData implements AutoCloseable {
    private final String nama;

    SumberData(String nama) {
        this.nama = nama;
        System.out.println("membuka " + nama);
    }

    @Override
    public void close() {
        System.out.println("menutup " + nama);
    }
}

public class ResourceSederhana {
    public static void main(String[] args) {
        try (SumberData basisdata = new SumberData("basisdata");
             SumberData jaringan = new SumberData("jaringan")) {
            System.out.println("badan try berjalan");
        }
        System.out.println("setelah blok");
    }
}

Output yang Dihasilkan:

text
membuka basisdata
membuka jaringan
badan try berjalan
menutup jaringan
menutup basisdata
setelah blok

Mari kita bedah urutan kejadiannya:

  1. Baris 1 (class SumberData implements AutoCloseable): class ini memenuhi syarat resource karena mengimplementasikan AutoCloseable dan menyediakan close().
  2. Baris 20-21 (try (SumberData basisdata = ...; SumberData jaringan = ...) {}): kedua resource dibuat saat deklarasi — basisdata lebih dulu, lalu jaringan. Urutan deklarasi ini yang menentukan urutan penutupan.
  3. Keluaran menutup jaringan lalu menutup basisdata: penutupan berjalan terbalik dari urutan pembukaan. Aturan ini disengaja agar resource yang dibuka paling akhir — biasanya yang paling bergantung pada resource lain — dilepas lebih dulu sebelum dependensinya.
  4. Tidak ada finally sama sekali: penutupan terjadi tanpa satu baris kode pembersihan dari kita, dan tetap berlaku baik saat badan try sukses maupun saat exception terbang keluar.

Membaca dengan Resource Nyata #

Pustaka standar penuh dengan class yang sudah mengimplementasikan AutoCloseable: BufferedReader, Scanner, FileInputStream, hingga koneksi JDBC. Contoh berikut membaca nama dari sebuah BufferedReader — penutupan otomatis menggantikan finally dari Chapter sebelumnya:

java BacaBaris.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;

public class BacaBaris {
    public static void main(String[] args) {
        String data = "Budi\nSiti\nDewi";

        try (BufferedReader pembaca = new BufferedReader(new StringReader(data))) {
            String baris;
            while ((baris = pembaca.readLine()) != null) {
                System.out.println("nama: " + baris);
            }
        } catch (IOException e) {
            System.out.println("gagal membaca: " + e.getMessage());
        }
    }
}

Output yang Dihasilkan:

text
nama: Budi
nama: Siti
nama: Dewi

Tiga nama tercetak berurutan, dan setelah loop selesai pembaca.close() dipanggil tanpa kita menuliskannya. Perhatikan juga blok catch (IOException e) di baris setelah kurung resource — readLine() mendeklarasikan IOException sehingga compiler meminta penanganan, dan pada try-with-resources posisi catch tetap sama seperti biasa. Untuk demo ini sumber datanya StringReader di memori sehingga pembacaan tidak mungkin gagal; pada berkas sungguhan di penyimpanan, kegagalan baca adalah skenario nyata yang harus ditangani.

Penutupan Tetap Terjadi Saat Kegagalan #

Pertanyaan penting: apa yang terjadi dengan penutupan saat badan try melempar exception? Jawabannya: close() tetap dipanggil sebelum exception sampai ke blok catch:

class Sambungan implements AutoCloseable {
    private final String nama;

    Sambungan(String nama) {
        this.nama = nama;
    }

    @Override
    public void close() {
        System.out.println("menutup " + nama);
    }
}

public class ResourceGagal {
    public static void main(String[] args) {
        try (Sambungan s = new Sambungan("kirim")) {
            System.out.println("mengirim data...");
            throw new IllegalStateException("jaringan terputus");
        } catch (IllegalStateException e) {
            System.out.println("ditangani: " + e.getMessage());
        }
        System.out.println("program berlanjut");
    }
}

Output yang Dihasilkan:

text
mengirim data...
menutup kirim
ditangani: jaringan terputus
program berlanjut

Urutan keluarannya membuktikan mekanismenya: mengirim data... tercetak, IllegalStateException dilempar, lalu menutup kirim berjalan, dan baru setelah itu blok catch menangani exception. Compiler menyusun ulang konstruksi ini menjadi pola finally-biasa di belakang layar — penutupan berada di jalur keluar mana pun — hanya saja beban menuliskannya diambil alih sepenuhnya.

Kita juga baru saja menyaksikan sesuatu yang menarik pada contoh BacaBaris: compiler menolak program jika IOException tidak ditangani, tetapi tidak pernah memaksa apa pun untuk IllegalStateException. Dari mana perbedaan perlakuan itu berasal? Chapter berikutnya membedah perbedaan mendasar antara checked dan unchecked exceptions — filosofi di baliknya dan dampaknya pada desain method kita.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

BacaBaris.java

Unduh berkas
java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;

public class BacaBaris {
    public static void main(String[] args) {
        String data = "Budi\nSiti\nDewi";

        try (BufferedReader pembaca = new BufferedReader(new StringReader(data))) {
            String baris;
            while ((baris = pembaca.readLine()) != null) {
                System.out.println("nama: " + baris);
            }
        } catch (IOException e) {
            System.out.println("gagal membaca: " + e.getMessage());
        }
    }
}

ResourceGagal.java

Unduh berkas
java
class Sambungan implements AutoCloseable {
    private final String nama;

    Sambungan(String nama) {
        this.nama = nama;
    }

    @Override
    public void close() {
        System.out.println("menutup " + nama);
    }
}

public class ResourceGagal {
    public static void main(String[] args) {
        try (Sambungan s = new Sambungan("kirim")) {
            System.out.println("mengirim data...");
            throw new IllegalStateException("jaringan terputus");
        } catch (IllegalStateException e) {
            System.out.println("ditangani: " + e.getMessage());
        }
        System.out.println("program berlanjut");
    }
}

ResourceSederhana.java

Unduh berkas
java
class SumberData implements AutoCloseable {
    private final String nama;

    SumberData(String nama) {
        this.nama = nama;
        System.out.println("membuka " + nama);
    }

    @Override
    public void close() {
        System.out.println("menutup " + nama);
    }
}

public class ResourceSederhana {
    public static void main(String[] args) {
        try (SumberData basisdata = new SumberData("basisdata");
             SumberData jaringan = new SumberData("jaringan")) {
            System.out.println("badan try berjalan");
        }
        System.out.println("setelah blok");
    }
}