Satu blok try sering menghadirkan lebih dari satu jenis kegagalan: masukan yang tidak bisa dikonversi, pembagian dengan nol, atau data yang belum terisi. Menangkap semuanya dengan satu catch (Exception e) membuat kita kehilangan kejelasan — tiap jenis kegagalan idealnya mendapat tanggapan yang tepat. Java menyediakan dua mekanisme untuk itu: beberapa blok catch berurutan untuk tanggapan yang berbeda-beda, dan sintaks multi-catch untuk menggabungkan beberapa tipe dalam satu blok tanggapan yang sama. Pada Chapter ini kita pelajari keduanya beserta aturan urutan yang ditegakkan compiler.

Blok catch Berurutan #

Blok catch boleh ditulis lebih dari satu setelah blok try, dan JVM memeriksa kecocokannya secara berurutan dari atas ke bawah. Blok pertama yang tipenya cocok dengan exception yang sedang terbang akan dijalankan; blok-blok setelahnya diabaikan untuk exception tersebut. Program berikut memproses beberapa masukan dengan tiga blok catch yang masing-masing menangani jenis kegagalan berbeda:

public class CatchBerurutan {
    static void proses(String teks) {
        System.out.println("memproses \"" + teks + "\"");
        try {
            int nilai = Integer.parseInt(teks.trim());
            System.out.println("  hasil: " + (100 / nilai));
        } catch (NumberFormatException e) {
            System.out.println("  teks bukan angka: " + e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println("  pembagian nol: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("  kegagalan lain: " + e.getClass().getSimpleName());
        }
    }

    public static void main(String[] args) {
        proses("25");
        proses(" abc ");
        proses("0");
        proses(null);
    }
}

Output yang Dihasilkan:

text
memproses "25"
  hasil: 4
memproses " abc "
  teks bukan angka: For input string: "abc"
memproses "0"
  pembagian nol: / by zero
memproses "null"
  kegagalan lain: NullPointerException

Mari kita bedah pemetaan kegagalan ke blok tanggapannya:

  1. Pemanggilan proses("25"): kedua statement di blok try berhasil — tidak ada catch yang berjalan, dan hasil 4 tercetak dari dalam try.
  2. Pemanggilan proses(" abc "): teks.trim() menghasilkan "abc", lalu Integer.parseInt melempar NumberFormatException — blok pertama yang cocok. Pencetakan 100 / nilai terlewati.
  3. Pemanggilan proses("0"): parsing berhasil, tetapi 100 / 0 melempar ArithmeticException — blok kedua yang berjalan.
  4. Pemanggilan proses(null): teks.trim() langsung melempar NullPointerException. Dua blok pertama tidak cocok tipenya, sehingga blok terakhir catch (Exception e) — penangkap umum — yang menangkap. Perhatikan keluaran memproses "null": penggabungan string dengan referensi null mencetak teks null, sehingga baris itu tetap tercetak sebelum kegagalan terjadi.

Urutan NumberFormatException → ArithmeticException → Exception pada contoh di atas bukan kebetulan. Karena pencocokan dilakukan dari atas, tipe yang lebih spesifik ditempatkan lebih dulu, dan tipe umum ditempatkan paling akhir sebagai jaring pengaman. Kalau kita membalik urutannya, seluruh exception akan tertangkap blok Exception lebih dulu dan blok-blok spesifik tidak pernah terpakai.

Urutan yang Salah Ditolak Compiler #

Masalahnya, compiler tidak membiarkan kita membuat blok spesifik yang mustahil terjangkau. Jika sebuah catch bertipe parent (misalnya Exception) diletakkan sebelum catch bertipe child-nya (misalnya NumberFormatException), compiler menolak program:

java UrutanSalah.java
public class UrutanSalah {
    public static void main(String[] args) {
        try {
            int nilai = Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("tangkap umum");
        } catch (NumberFormatException e) {
            System.out.println("tangkap khusus");
        }
    }
}

Kompilasi berakhir dengan pesan error: exception NumberFormatException has already been caught yang menunjuk baris catch (NumberFormatException e). Alasannya lugas: NumberFormatException adalah turunan Exception, dan blok catch (Exception e) di atasnya sudah menangkap semua exception bertipe Exception beserta turunannya — blok kedua tidak akan pernah bisa dijalankan, sehingga compiler menolak kode yang tidak berarti itu.

Multi-catch dengan Tanda Pipa #

Sering kali dua jenis kegagalan justru butuh tanggapan yang sama persis. Daripada menduplikasi isi blok, Java sejak versi 7 menyediakan sintaks multi-catch: beberapa tipe ditulis dalam satu klausa catch dan dipisah tanda pipa (|). Contoh klasiknya adalah membaca dan mengonversi isi stream — kegagalan baca (IOException) dan kegagalan konversi (NumberFormatException) sama-sama cukup dilaporkan sebagai baris yang tidak bisa diproses:

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

public class MultiCatch {
    public static void main(String[] args) throws IOException {
        BufferedReader pembaca = new BufferedReader(new StringReader("12\nabc"));

        for (int i = 1; i <= 2; i++) {
            try {
                String baris = pembaca.readLine();
                int nilai = Integer.parseInt(baris);
                System.out.println("baris " + i + ": " + nilai);
            } catch (IOException | NumberFormatException e) {
                System.out.println("baris " + i + " gagal: " + e.getClass().getSimpleName());
            }
        }

        pembaca.close();
    }
}

Output yang Dihasilkan:

text
baris 1: 12
baris 2 gagal: NumberFormatException

Mari kita amati detail pentingnya:

  1. Baris 6 (main(String[] args) throws IOException): pembaca.close() di akhir bisa melempar IOException yang berada di luar blok try, sehingga main harus menyatakan kemungkinan itu lewat klausa throws — detail lengkapnya dibahas pada Chapter throw dan throws.
  2. Baris 12 (catch (IOException | NumberFormatException e)): satu variabel e untuk dua tipe. Baris pertama berhasil dikonversi menjadi 12; baris kedua mengandung "abc" sehingga NumberFormatException tertangkap di sini.
  3. Baris 13 (e.getClass().getSimpleName()): pada multi-catch, tipe statis e adalah parent terdekat bersama dari kedua tipe — di sini Exception — sehingga getClass().getSimpleName() menjadi cara tepat untuk mengetahui jenis kegagalan yang sebenarnya saat program berjalan.

Kapan memilih yang mana? Jika dua kegagalan ditangani dengan kode yang identik, gabungkan dengan | agar tidak ada duplikasi. Jika tanggapannya berbeda — pesan berbeda, pemulihan berbeda — pisahkan menjadi blok catch berurutan dari yang paling spesifik ke yang paling umum.

Setelah blok catch selesai, satu pertanyaan kerap muncul: bagaimana menjamin pembersihan — menutup berkas, memutus koneksi — tetap berjalan baik saat try sukses maupun saat exception terbang? Jawabannya adalah blok finally, topik Chapter berikutnya.

Referensi #

Berkas Sumber

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

CatchBerurutan.java

Unduh berkas
java
public class CatchBerurutan {
    static void proses(String teks) {
        System.out.println("memproses \"" + teks + "\"");
        try {
            int nilai = Integer.parseInt(teks.trim());
            System.out.println("  hasil: " + (100 / nilai));
        } catch (NumberFormatException e) {
            System.out.println("  teks bukan angka: " + e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println("  pembagian nol: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("  kegagalan lain: " + e.getClass().getSimpleName());
        }
    }

    public static void main(String[] args) {
        proses("25");
        proses(" abc ");
        proses("0");
        proses(null);
    }
}

MultiCatch.java

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

public class MultiCatch {
    public static void main(String[] args) throws IOException {
        BufferedReader pembaca = new BufferedReader(new StringReader("12\nabc"));

        for (int i = 1; i <= 2; i++) {
            try {
                String baris = pembaca.readLine();
                int nilai = Integer.parseInt(baris);
                System.out.println("baris " + i + ": " + nilai);
            } catch (IOException | NumberFormatException e) {
                System.out.println("baris " + i + " gagal: " + e.getClass().getSimpleName());
            }
        }

        pembaca.close();
    }
}

UrutanSalah.java

Unduh berkas
java
public class UrutanSalah {
    public static void main(String[] args) {
        try {
            int nilai = Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("tangkap umum");
        } catch (NumberFormatException e) {
            System.out.println("tangkap khusus");
        }
    }
}