try, catch, dan finally

Menangani exception dengan multi-catch sambil memahami hierarki Throwable, Error, dan Exception.

Daftar isi

Exception membuat program yang sedang berjalan tetap terkendali ketika kejadian tak terduga muncul, misalnya masukan yang bukan angka atau indeks array di luar batas. Topik ini membahas tiga penyusun utama penanganan exception di Java, yaitu blok try, blok catch, dan blok finally, ditambah multi-catch untuk menangani beberapa tipe exception dalam satu blok. Struktur hierarki Throwable, Error, dan Exception juga diurai lebih dahulu, karena blok catch memilih exception yang ditampungnya berdasarkan hubungan pewarisan antarkelas.

Hierarki Throwable, Error, dan Exception#

Semua exception di Java berakar pada kelas java.lang.Throwable, sehingga hanya objek kelas itu atau turunannya yang dapat dilempar dan ditangkap. Dari Throwable bercabang dua keluarga besar. Kelas Error melaporkan masalah serius pada lingkungan runtime, misalnya memori habis (OutOfMemoryError) atau tumpukan pemanggilan penuh (StackOverflowError). Kondisi semacam itu umumnya tidak dapat dipulihkan oleh kode aplikasi, sehingga Error tidak dimaksudkan untuk ditangkap. Kelas Exception menampung kejadian tak terduga yang wajar ditangani program, seperti kegagalan mengubah teks menjadi angka.

Di bawah Exception terdapat RuntimeException, cabang yang berisi exception akibat kesalahan pemrograman seperti NullPointerException dan ArithmeticException. Cabang selain RuntimeException, misalnya IOException, disebut checked exception karena kompilator menuntut penanganannya, sedangkan RuntimeException beserta turunannya disebut unchecked exception. Gambar berikut memetakan hierarki tersebut beserta contoh kelas pada setiap cabang.

Throwablepuncak hierarkiErrortidak untuk ditanganiExceptionkelas dasar penangananOutOfMemoryErrorStackOverflowErrorIOExceptionSQLExceptionRuntimeExceptionNullPointerExceptionArithmeticExceptionIllegalArgumentExceptionError: tidak untuk ditanganichecked: wajib ditanganiunchecked: opsional
Hierarki Throwable beserta contoh kelas pada tiap cabang

Menangkap exception dengan try dan catch#

Blok try mengapit kode yang berpotensi melempar exception, sedangkan blok catch menampung exception tersebut agar program bisa melanjutkan eksekusi alih-alih berhenti. Pemilihan tipe pada catch menentukan kegagalan mana yang ditampung, sedangkan kegagalan lain tetap bergerak naik ke pemanggilnya. Program berikut membaca dua teks menjadi angka, dan teks kedua sengaja tidak sah.

java DemoTryCatch.java
public class DemoTryCatch {

    public static void main(String[] args) {
        String[] masukan = {"150", "nol koma lima"};
        for (String teks : masukan) {
            try {
                int angka = Integer.parseInt(teks);
                System.out.println("\"" + teks + "\" berhasil dibaca: " + angka);
            } catch (NumberFormatException e) {
                System.out.println("\"" + teks + "\" bukan angka: " + e.getMessage());
            }
            System.out.println("Perulangan berlanjut");
        }
    }
}
text Keluaran
"150" berhasil dibaca: 150
Perulangan berlanjut
"nol koma lima" bukan angka: For input string: "nol koma lima"
Perulangan berlanjut

Menjalankannya dari terminal cukup dengan satu perintah berikut.

bash
# berada di folder tempat DemoTryCatch.java disimpan
java DemoTryCatch.java
text Keluaran
"150" berhasil dibaca: 150
Perulangan berlanjut
"nol koma lima" bukan angka: For input string: "nol koma lima"
Perulangan berlanjut

Metode Integer.parseInt melempar NumberFormatException ketika argumennya tidak dapat diubah menjadi int. Untuk teks "150", konversi berhasil sehingga blok catch dilewati sama sekali. Untuk teks kedua, exception terlempar, eksekusi di dalam try langsung berhenti pada baris pemanggilan parseInt, lalu blok catch menampung objek exceptionnya dalam variabel e. Metode getMessage() mengembalikan pesan yang menyertai lemparan. Setelah blok catch selesai, program berlanjut ke pernyataan setelahnya, tampak dari cetakan Perulangan berlanjut yang muncul dua kali.

Menggabungkan catch dengan multi-catch#

Multi-catch menggabungkan beberapa tipe exception dalam satu blok catch dengan tanda pipa, sehingga penanganan yang sama tidak perlu ditulis berulang. Tanpa fitur ini, dua blok catch dengan tubuh yang hampir sama harus ditulis berurutan, dan perbedaannya mudah terlewat saat disunting. Program berikut membagi dua angka yang masuk sebagai teks, dan kegagalannya bisa datang dari dua arah.

java DemoMultiCatch.java
public class DemoMultiCatch {

    public static void main(String[] args) {
        System.out.println(hitung("12", "4"));
        System.out.println(hitung("12", "nol"));
        System.out.println(hitung("12", "0"));
    }

    static int hitung(String teksA, String teksB) {
        try {
            int a = Integer.parseInt(teksA);
            int b = Integer.parseInt(teksB);
            return a / b;
        } catch (NumberFormatException | ArithmeticException e) {
            System.out.println("Masukan tidak sah (" + e.getClass().getSimpleName() + ")");
            return 0;
        }
    }
}
text Keluaran
3
Masukan tidak sah (NumberFormatException)
0
Masukan tidak sah (ArithmeticException)
0

Metode hitung memuat dua sumber kegagalan, yaitu parseInt yang melempar NumberFormatException dan pembagian bulat dengan nol yang melempar ArithmeticException. Keduanya dinyatakan dalam satu catch dengan pemisah pipa, dan variabel e bertipe kelas dasar terdekat dari keduanya. Pemanggilan e.getClass().getSimpleName() menunjukkan tipe exception yang benar-benar terlempar pada setiap kasus, sehingga satu blok tetap mampu membedakan sumber kegagalan bila diperlukan.

Urutan blok catch mengikuti aturan pewarisan. Blok untuk kelas yang lebih umum harus ditulis setelah blok kelas turunannya, karena catch dievaluasi berurutan dari atas ke bawah.

java Contoh: urutan catch yang ditolak kompilator
// tidak-dijalankan
try {
    int angka = Integer.parseInt(teks);
} catch (Exception e) {
    System.out.println("Penangan yang terlalu umum");
} catch (NumberFormatException e) {
    System.out.println("Tidak pernah tercapai");
}

Kompilator menolak potongan di atas karena Exception sudah mencakup NumberFormatException, sehingga blok kedua mustahil tercapai. Biasanya yang ditulis adalah catch paling spesifik lebih dahulu, lalu catch yang lebih umum setelahnya bila memang dibutuhkan.

Menjamin pembersihan dengan finally#

Blok finally menampung kode yang harus dijalankan apa pun yang terjadi di dalam try, baik eksekusi selesai normal, exception tertangkap, maupun exception lolos tanpa tertangkap. Tugasnya bukan menangani exception, melainkan menjamin pembersihan, sehingga finally biasanya berpasangan dengan catch yang sudah ada. Bentuk try-catch-finally adalah susunan penuhnya, dan bentuk try-finally tanpa catch tetap sah.

java DemoFinally.java
public class DemoFinally {

    public static void main(String[] args) {
        System.out.println("-- Kasus normal");
        proses("48");
        System.out.println("-- Kasus gagal");
        proses("empat");
    }

    static void proses(String teks) {
        try {
            System.out.println("Angka: " + Integer.parseInt(teks));
        } catch (NumberFormatException e) {
            System.out.println("Ditangkap di proses: " + e.getMessage());
        } finally {
            System.out.println("Blok finally dijalankan");
        }
    }
}
text Keluaran
-- Kasus normal
Angka: 48
Blok finally dijalankan
-- Kasus gagal
Ditangkap di proses: For input string: "empat"
Blok finally dijalankan

Cetakan Blok finally dijalankan muncul pada dua kasus, baik ketika parseInt berhasil maupun gagal. Bila exception di dalam try tidak ditangkap oleh catch mana pun, finally tetap dieksekusi sebelum exception bergerak naik ke pemanggil. Sifat inilah yang menjadikan finally tempat menutup koneksi, berkas, atau sumber daya lain yang penutupannya tidak boleh terlewat. Pola try-finally tanpa catch kini jarang ditulis karena try-with-resources memberikan jaminan penutupan yang lebih ringkas.

Kesalahan umum#

  • Menangkap Exception atau Throwable demi menutup semua kemungkinan. Blok yang terlalu umum menampung pula bug yang seharusnya terlihat, seperti NullPointerException. Tangkap tipe yang paling sesuai dengan kegagalan yang memang diantisipasi.
  • Menulis catch (Exception e) sebelum catch turunannya. Blok di bawahnya mustahil tercapai dan kompilasi ditolak. Letakkan catch yang paling spesifik lebih dahulu.
  • Membiarkan blok catch kosong. Exception lenyap tanpa jejak, sehingga kegagalan baru terlihat jauh setelah penyebabnya. Catat kejadiannya atau lempar kembali secara sadar.
  • Menganggap finally menggantikan nilai kembalian. return di dalam try tetap menentukan nilai yang diserahkan, sedangkan tugas finally hanya menjalankan pembersihan.

Ringkasan#

  • Semua exception berakar pada Throwable, yang bercabang menjadi Error untuk masalah lingkungan runtime dan Exception untuk kejadian yang wajar ditangani program.
  • try mengapit kode yang berpotensi gagal, catch menampung exception berdasarkan tipenya, dan blok untuk tipe paling spesifik ditulis lebih dahulu.
  • Multi-catch menggabungkan beberapa tipe dalam satu blok catch dengan tanda pipa.
  • finally dijalankan apa pun hasil eksekusi try, sehingga cocok untuk pembersihan yang tidak boleh terlewat.

Referensi#