Sejauh ini exception yang kita tangani semuanya datang dari pustaka standar — hasil panggilan Integer.parseInt atau operasi I/O. Saat menulis method sendiri, kita juga berhak — dan sering wajib — melempar exception atas nama kondisi yang kita kenali lebih dulu. Java menyediakan dua kata kunci yang bentuknya mirip tetapi perannya jauh berbeda: throw adalah statement yang benar-benar melempar sebuah objek exception, sedangkan throws adalah klausa pada tanda tangan method yang menyatakan kemungkinan kegagalan. Pada Chapter ini kita gunakan keduanya, termasuk pola melempar ulang (rethrow) sebuah exception yang sudah tertangkap.

Statement throw untuk Melempar #

Statement throw diikuti sebuah instance Throwable — hampir selalu dibuat baru saat itu juga dengan new. Begitu statement tersebut dieksekusi, eksekusi method berhenti di titik itu dan objek exception mulai merambat ke atas. Pola paling umum: validasi argumen atau keadaan, lalu lempar jika tidak sah:

java ThrowDasar.java
public class ThrowDasar {
    static int bacaUmur(String teks) {
        int umur = Integer.parseInt(teks);
        if (umur < 0 || umur > 120) {
            throw new IllegalStateException("umur tidak masuk akal: " + umur);
        }
        return umur;
    }

    public static void main(String[] args) {
        String[] masukan = {"17", "150"};
        for (String teks : masukan) {
            try {
                System.out.println("umur: " + bacaUmur(teks));
            } catch (IllegalStateException e) {
                System.out.println("ditolak: " + e.getMessage());
            }
        }
    }
}

Output yang Dihasilkan:

text
umur: 17
ditolak: umur tidak masuk akal: 150

Mari kita bedah perannya:

  1. Baris 5-6 (if (umur < 0 || umur > 120)): method menetapkan kontraknya sendiri — umur yang sah hanya 0 sampai 120. Kontrak ini tidak bisa diungkapkan lewat tipe data int, sehingga pemeriksaan berjalan saat runtime.
  2. Baris 6 (throw new IllegalStateException("umur tidak masuk akal: " + umur)): objek exception dibuat dengan pesan yang menyertakan nilai bermasalah, lalu dilempar. Statement return umur di bawahnya tidak dieksekusi untuk masukan yang ditolak.
  3. Baris 15 (catch (IllegalStateException e)): pemanggil menangkap dan membaca pesan umur tidak masuk akal: 150. Karena IllegalStateException adalah turunan RuntimeException, tidak ada klausa throws yang perlu ditulis pada bacaUmur — kita memilih unchecked karena masukan seperti ini menandakan bug di sisi pemanggil.

Klausa throws untuk Menyatakan #

Klausa throws tidak melempar apa pun; klausa ini adalah pengumuman pada tanda tangan method: “method ini bisa mengakhiri dirinya dengan exception bertipe berikut”. Klausa ini wajib untuk checked exception yang tidak ditangani di dalam method, dan pemanggil membacanya seperti membaca bagian dari kontrak. Untuk unchecked exception, klausa throws bersifat opsional — kehadirannya hanya dokumentasi. Perbedaan keduanya dirangkum berikut:

Aspekthrowthrows
Kategoristatement di badan methodklausa di tanda tangan method
Diikutisatu objek exception (new IllegalStateException(...))daftar tipe exception
Berapa kaliboleh banyak statement per methodditulis sekali dengan tipe berbeda-beda
Efek saat dieksekusi/dibacaeksekusi berhenti, exception merambattidak mengeksekusi apa pun; memberi tahu pemanggil dan compiler

Rantai pemanggilan berikut memperlihatkan klausa throws yang berdiri di beberapa level sekaligus:

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

public class ThrowsRantai {
    static String bacaNama() throws IOException {
        try (BufferedReader pembaca = new BufferedReader(new StringReader("Budi"))) {
            return pembaca.readLine();
        }
    }

    static String sapaPengguna() throws IOException {
        String nama = bacaNama();
        return "Halo, " + nama + "!";
    }

    public static void main(String[] args) {
        try {
            System.out.println(sapaPengguna());
        } catch (IOException e) {
            System.out.println("gagal: " + e.getClass().getSimpleName());
        }
    }
}

Output yang Dihasilkan:

text
Halo, Budi!

Alurnya berjalan seperti ini: main memanggil sapaPengguna, method itu memanggil bacaNama, dan bacaNama membaca satu baris lewat BufferedReader. Method bacaNama menyatakan throws IOException karena readLine() bisa gagal; sapaPengguna tidak menangkap kemungkinan itu, sehingga method tersebut wajib menyatakan ulang throws IOException ke pemanggilnya sendiri. Akhirnya main yang mengambil keputusan nyata: menangkap dengan catch. Perhatikan bahwa pada contoh ini StringReader tidak pernah gagal di runtime — keluaran program tetap Halo, Budi! — tetapi kontrak tetap tercatat di tanda tangan, siap dipakai saat sumber datanya berganti berkas sungguhan.

Melempar Ulang Exception #

Kadang method menangkap exception bukan untuk menyelesaikannya, melainkan untuk mencatat sesuatu dulu — lalu meneruskan tanggung jawab ke pemanggil. Statement throw di dalam blok catch menjawab kebutuhan itu, dan objek yang dilempar ulang bisa exception yang sama:

java Rethrow.java
public class Rethrow {
    static void prosesPesanan(String id) {
        try {
            int nomor = Integer.parseInt(id);
            System.out.println("pesanan " + nomor + " diproses");
        } catch (NumberFormatException e) {
            System.out.println("mencatat ke log: id pesanan salah -> " + id);
            throw e; // melempar ulang exception yang sama
        }
    }

    public static void main(String[] args) {
        try {
            prosesPesanan("A-17");
        } catch (NumberFormatException e) {
            System.out.println("pemanggil menerima: " + e.getMessage());
        }
    }
}

Output yang Dihasilkan:

text
mencatat ke log: id pesanan salah -> A-17
pemanggil menerima: For input string: "A-17"

Method prosesPesanan mencatat masalah id A-17 lebih dulu, lalu melempar ulang exception yang sama — bukan yang baru. Hasilnya terlihat pada keluaran: log tercatat, dan main tetap menerima NumberFormatException dengan pesan aslinya For input string: "A-17". Stack trace exception asli juga ikut utuh, sehingga titik kejadian pertama tetap bisa ditelusuri. Variasi lain dari pola ini adalah melempar exception baru yang membungkus exception lama sebagai cause — berguna saat kita ingin menerjemahkan kegagalan level rendah ke bahasa domain kita; teknik pembungkusan tersebut dibahas khusus pada Chapter Exception Chaining.

Dengan throw dan throws di tangan, satu keinginan alami muncul: exception bawaan seperti IllegalStateException bicara dalam bahasa umum — “keadaan tidak sah” — sedangkan aplikasi kita punya kosakata kegagalannya sendiri. Bagaimana kalau kegagalan penarikan saldo bisa dipanggil dengan namanya sendiri? Chapter berikutnya mengajari kita membuat custom exception milik domain kita.

Referensi #

Berkas Sumber

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

Rethrow.java

Unduh berkas
java
public class Rethrow {
    static void prosesPesanan(String id) {
        try {
            int nomor = Integer.parseInt(id);
            System.out.println("pesanan " + nomor + " diproses");
        } catch (NumberFormatException e) {
            System.out.println("mencatat ke log: id pesanan salah -> " + id);
            throw e; // melempar ulang exception yang sama
        }
    }

    public static void main(String[] args) {
        try {
            prosesPesanan("A-17");
        } catch (NumberFormatException e) {
            System.out.println("pemanggil menerima: " + e.getMessage());
        }
    }
}

ThrowDasar.java

Unduh berkas
java
public class ThrowDasar {
    static int bacaUmur(String teks) {
        int umur = Integer.parseInt(teks);
        if (umur < 0 || umur > 120) {
            throw new IllegalStateException("umur tidak masuk akal: " + umur);
        }
        return umur;
    }

    public static void main(String[] args) {
        String[] masukan = {"17", "150"};
        for (String teks : masukan) {
            try {
                System.out.println("umur: " + bacaUmur(teks));
            } catch (IllegalStateException e) {
                System.out.println("ditolak: " + e.getMessage());
            }
        }
    }
}

ThrowsRantai.java

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

public class ThrowsRantai {
    static String bacaNama() throws IOException {
        try (BufferedReader pembaca = new BufferedReader(new StringReader("Budi"))) {
            return pembaca.readLine();
        }
    }

    static String sapaPengguna() throws IOException {
        String nama = bacaNama();
        return "Halo, " + nama + "!";
    }

    public static void main(String[] args) {
        try {
            System.out.println(sapaPengguna());
        } catch (IOException e) {
            System.out.println("gagal: " + e.getClass().getSimpleName());
        }
    }
}