Pernyataan throw melempar sebuah exception, sedangkan kata kunci throws mendeklarasikan exception yang mungkin keluar dari suatu metode. Keduanya bekerja pada sisi yang berbeda dari kejadian yang sama, yaitu pelemparan di dalam tubuh metode dan pengumumannya pada tanda tangan metode. Topik ini membedakan keduanya, membentuk pesan lemparan yang berguna bagi pembaca log, lalu menutup dengan exception kustom yang membungkus penyebab aslinya.
Melempar exception dengan throw#
Pernyataan throw melempar satu objek turunan Throwable dan menghentikan eksekusi pada titik itu, sehingga pernyataan sesudahnya tidak pernah dijalankan. Objek yang dilempar biasanya dibuat langsung dengan new, dan argumen stringnya menjadi pesan yang terbaca saat exception ditangkap. Program berikut memvalidasi rentang nilai ujian sebelum menghitung bobot sederhananya.
public class DemoThrow {
public static void main(String[] args) {
System.out.println("Bobot 90 : " + hitungBobot(90));
try {
System.out.println("Bobot 120 : " + hitungBobot(120));
} catch (IllegalArgumentException e) {
System.out.println("Ditolak: " + e.getMessage());
}
}
static double hitungBobot(double nilai) {
if (nilai < 0 || nilai > 100) {
throw new IllegalArgumentException("nilai di luar rentang 0-100: " + nilai);
}
return nilai / 10;
}
}Bobot 90 : 9.0
Ditolak: nilai di luar rentang 0-100: 120.0Menjalankannya dari terminal cukup dengan satu perintah berikut.
# berada di folder tempat DemoThrow.java disimpan
java DemoThrow.javaBobot 90 : 9.0
Ditolak: nilai di luar rentang 0-100: 120.0Pemanggilan pertama lolos pemeriksaan sehingga metode mengembalikan hasilnya seperti biasa. Pada pemanggilan kedua, kondisi nilai > 100 terpenuhi, pernyataan throw melempar objek IllegalArgumentException, dan metode langsung berhenti tanpa menyentuh perhitungan di bawahnya. Exception itu bergerak naik ke pemanggil terdekat yang catch-nya cocok, yaitu blok di dalam main. Pesan lemparannya memuat nilai yang ditolak, sehingga pembaca log langsung tahu masukan mana yang bermasalah tanpa perlu membuka kode.
Pemilihan tipe exception mengikuti makna kegagalan, bukan selera. IllegalArgumentException cocok untuk argumen yang ditolak. IllegalStateException cocok bila objek dipakai pada keadaan yang tidak tepat. UnsupportedOperationException cocok untuk operasi yang memang tidak didukung. Keluarga generik seperti Exception mentah hanya dipakai bila tidak ada turunan yang lebih pas, karena tipe itulah yang dicari oleh blok catch pemanggil.
Mendeklarasikan exception dengan throws#
Kata kunci throws ditulis pada deklarasi metode untuk menyatakan checked exception yang mungkin dilempar metode tersebut tanpa ditangani di dalamnya. Bentuknya berupa daftar tipe yang dipisah koma, misalnya throws IOException, SQLException. Deklarasi ini memindahkan kewajiban penanganan kepada setiap pemanggil, sesuai aturan tangkap atau nyatakan. Metode berikut mendelegasikan pencarian kelas ke Class.forName dan meneruskan kewajibannya.
public class DemoThrows {
public static void main(String[] args) {
try {
Class<?> kelas = bacaKelas("java.util.ArrayList");
System.out.println("Berhasil: " + kelas.getName());
bacaKelas("java.util.TidakAda");
} catch (ClassNotFoundException e) {
System.out.println("Gagal: " + e.getMessage());
}
}
static Class<?> bacaKelas(String nama) throws ClassNotFoundException {
return Class.forName(nama);
}
}Berhasil: java.util.ArrayList
Gagal: java.util.TidakAdaTanpa deklarasi throws pada bacaKelas, pemanggilan Class.forName di dalamnya tidak akan lolos kompilasi. Deklarasi itu membuat tanda tangan metode jujur, yaitu setiap pembaca kode tahu kegagalan apa yang harus diantisipasi tanpa membaca tubuh metodenya. Metode main juga boleh mendeklarasikan throws, tetapi akibatnya exception yang lolos menyebabkan JVM mencetak stack trace lalu menghentikan program, sehingga pola itu hanya sesuai untuk contoh singkat. Deklarasi throws untuk unchecked exception tetap sah, tetapi jarang ditulis karena kompilator tidak menuntutnya.
Pernyataan throw juga sah di dalam blok catch untuk melempar kembali exception yang sedang ditangkap, baik objek aslinya maupun pembungkusnya. Bentuk paling sederhananya adalah throw e, yang meneruskan objek yang sama ke pemanggil. Pola ini menjadi dasar pembungkusan pada bagian berikutnya, dan biasanya dilakukan setelah konteks baru ditambahkan pada pesannya.
Exception kustom dan pembungkusan penyebab#
Exception kustom dibuat dengan mewarisi Exception atau salah satu turunannya, memberi nama domain pada kegagalan yang muncul berulang. Nama seperti UsiaTidakSahException menyampaikan maksud kegagalan dengan jelas, sedangkan tipe pustaka seperti NumberFormatException menyampaikan penyebab teknisnya. Kedua lapisan itu digabungkan lewat pembungkusan, yakni melempar exception baru sambil menyimpan exception lama sebagai penyebabnya.
public class DemoEksepsiKustom {
public static void main(String[] args) {
System.out.println("Usia: " + bacaUsia("17"));
try {
System.out.println("Usia: " + bacaUsia("tujuh belas"));
} catch (UsiaTidakSahException e) {
System.out.println("Gagal: " + e.getMessage());
System.out.println("Penyebab: " + e.getCause());
}
}
static int bacaUsia(String teks) {
try {
return Integer.parseInt(teks);
} catch (NumberFormatException e) {
throw new UsiaTidakSahException("\"" + teks + "\" bukan bilangan bulat", e);
}
}
}
class UsiaTidakSahException extends IllegalArgumentException {
UsiaTidakSahException(String pesan, Throwable penyebab) {
super(pesan, penyebab);
}
}Usia: 17
Gagal: "tujuh belas" bukan bilangan bulat
Penyebab: java.lang.NumberFormatException: For input string: "tujuh belas"Kelas UsiaTidakSahException mewarisi IllegalArgumentException, sehingga tetap termasuk keluarga unchecked dan pemanggil tidak dibebani deklarasi. Konstruktornya meneruskan pesan dan penyebab ke konstruktor induk lewat super. Pada bacaUsia, NumberFormatException ditangkap lalu dibungkus menjadi exception domain dengan exception asli sebagai penyebab. Metode getCause() mengembalikan objek penyebab itu, dan stack trace yang dicetak JVM juga menampilkan rantainya sebagai Caused by. Pemanggil cukup menangkap satu tipe domain, sedangkan detail teknis di lapisan bawah tetap terjaga.
Mewarisi Exception alih-alih IllegalArgumentException menghasilkan checked exception, yang wajib ditangkap oleh setiap pemanggil. Bentuk lemparannya tidak berubah, hanya kewajiban pemanggilnya yang bertambah. Pilihannya kembali ke pertimbangan yang sama, yaitu apakah pemanggil memang punya rencana pemulihan terhadap kegagalan tersebut.
Kesalahan umum#
- Menangkap exception hanya untuk melempar ulang tipe yang sama tanpa menambah apa pun. Jejak yang sama tercetak dua kali di log. Lempar ulang hanya bila ada konteks baru yang ditambahkan.
- Menulis pesan lemparan yang tidak informatif, misalnya hanya
data tidak sah. Sertakan nilai yang ditolak dan batasan yang dilanggar. - Membuat kelas exception kustom untuk setiap metode. Nama khusus dibuat bila kegagalan itu muncul di beberapa tempat dan pemanggil memang membedakannya, bukan sekadar variasi pesan.
- Melempar exception dari konstruktor kustom tanpa menetapkan penyebabnya, padahal kegagalan berasal dari exception lain. Gunakan konstruktor dengan parameter
Throwable.
Ringkasan#
throwmelempar satu objek exception dan menghentikan eksekusi pada titik lemparan.throwsmendeklarasikan checked exception yang mungkin keluar dari metode, sehingga kewajiban penanganan pindah ke pemanggil.- Exception kustom mewarisi
Exceptionatau turunannya dan menamai kegagalan dengan istilah domain. - Pembungkusan dengan konstruktor
(pesan, penyebab)menjaga jejak kegagalan asli dan tetap terbaca lewatgetCause().
Referensi#
- How to Throw Exceptions — Oracle — aturan pelemparan exception dan pemilihan tipe yang tepat.
- Specifying the Exceptions Thrown by a Method — Oracle — aturan deklarasi throws beserta contohnya.
- Chained Exceptions — Oracle — pembungkusan exception dengan penyebabnya lewat konstruktor bertingkat.