Kita sudah dua kali menyaksikan perlakuan yang berbeda dari compiler: IOException tidak boleh dibiarkan begitu saja, sedangkan IllegalStateException dan NumberFormatException lolos tanpa penanganan apa pun. Pembedanya adalah pembagian yang diwariskan sejak Java 1.0: exception di dalam cabang RuntimeException (serta seluruh cabang Error) disebut unchecked, sedangkan exception lain di cabang Exception disebut checked. Pada Chapter ini kita bedah filosofi keduanya, melihat compiler menolak program yang melanggar aturannya, dan belajar memilih jenis yang tepat saat merancang method sendiri.
Filosofi Dua Kelompok #
Perbedaan keduanya bukan soal tingkat keparahan, melainkan siapa yang paling berwenang mengetahui kegagalan tersebut. Checked exception dipakai untuk kegagalan yang bisa dipulihkan dan yang pemanggil wajib sadar sebelum memakai method: berkas tidak ada, koneksi basis data terputus, atau stream habis. Compiler memaksa pemanggil memilih — menangkap dengan catch, atau meneruskan kemungkinan itu ke pemanggilnya lagi lewat throws — supaya keputusan penanganan tidak tenggelam.
Unchecked exception (RuntimeException dan turunannya) dipakai untuk kegagalan yang mencerminkan bug program: mengakses anggota objek null, mengakses indeks di luar batas, atau memberi argumen yang mustahil. Bug tidak seharusnya “ditangani” di tempat terjadi — bug harus diperbaiki. Karena itu compiler sengaja tidak memaksa pemeriksaan; jika bug itu lolos sampai runtime, muncullah stack trace yang menunjuk langsung ke baris yang harus diperbaiki.
| Aspek | Checked exception | Unchecked exception |
|---|---|---|
| Posisi hierarki | turunan Exception di luar RuntimeException | turunan RuntimeException (dan Error) |
| Contoh | IOException, SQLException, InterruptedException | NullPointerException, NumberFormatException, IllegalArgumentException |
| Pemeriksaan compiler | wajib catch atau throws | tidak diperiksa |
| Makna kegagalan | kondisi lingkungan yang bisa dipulihkan | bug logika program |
| Penanganan ideal | pulihkan, ulangi, atau laporkan ke pengguna | perbaiki kode penyebabnya |
Compiler Memaksa Penanganan Checked Exception #
Mari kita lihat compiler menolak program secara nyata. Method bacaBarisPertama mendeklarasikan throws IOException karena operasi bacanya memang bisa gagal; pemanggilnya di main mencoba mengabaikan fakta itu:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class TanpaMenangani {
static String bacaBarisPertama() throws IOException {
try (BufferedReader pembaca = new BufferedReader(new StringReader("satu\ndua"))) {
return pembaca.readLine();
}
}
public static void main(String[] args) {
System.out.println("baris: " + bacaBarisPertama());
}
}Kompilasi gagal dengan pesan error: unreported exception IOException; must be caught or declared to be thrown yang menunjuk pemanggilan bacaBarisPertama() di baris main. Compiler membaca klausa throws IOException pada method tersebut, menyimpulkan bahwa eksekusi main bisa berhenti di titik itu, dan menolak melanjutkan tanpa keputusan penanganan. Program semacam ini bahkan tidak pernah sampai ke JVM — kegagalan tertangkap tepat di tahap kompilasi.
Dua jalan kelulinya: menangkap, atau menyatakan ulang. Versi pertama membungkus pemanggilan dalam try-catch:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class CheckedDitangani {
static String bacaBarisPertama() throws IOException {
try (BufferedReader pembaca = new BufferedReader(new StringReader("satu\ndua"))) {
return pembaca.readLine();
}
}
public static void main(String[] args) {
try {
System.out.println("baris: " + bacaBarisPertama());
} catch (IOException e) {
System.out.println("gagal membaca: " + e.getClass().getSimpleName());
}
}
}Output yang Dihasilkan:
baris: satuPemanggilan pertama di dalam try berjalan mulus sehingga baris: satu tercetak; catch hanya berjalan jika IOException sungguhan terjadi. Versi kedua — menulis throws IOException pada tanda tangan main — juga sah: kemungkinan kegagalan diteruskan ke pemanggil main, yaitu launcher JVM, dan bila kegagalan itu benar-benar terjadi, default handler JVM yang akan menampilkan stack trace-nya.
Unchecked Exception Bebas dari Paksaan #
Sekarang bandingkan dengan method yang melempar exception unchecked. Method berikut memvalidasi argumen dengan IllegalArgumentException tanpa klausa throws sama sekali, dan program tetap dikompilasi meskipun pemanggil pertama tidak menangkap apa pun:
public class UncheckedBebas {
static int kecepatan(int nilai) {
if (nilai < 0) {
throw new IllegalArgumentException("kecepatan tidak boleh negatif: " + nilai);
}
return nilai;
}
public static void main(String[] args) {
System.out.println("kecepatan: " + kecepatan(60));
try {
System.out.println("kecepatan: " + kecepatan(-10));
} catch (IllegalArgumentException e) {
System.out.println("ditolak: " + e.getMessage());
}
}
}Output yang Dihasilkan:
kecepatan: 60
ditolak: kecepatan tidak boleh negatif: -10Pemanggilan kecepatan(60) tidak dibungkus apa pun dan compiler tidak protes — IllegalArgumentException adalah turunan RuntimeException, jadi bebas dari pemeriksaan. Pemanggilan kedua tetap kita bungkus try-catch bukan karena kewajiban compiler, melainkan karena keputusan sadar: pada titik itu program ingin menolak masukan negatif dengan pesan yang jelas alih-alih berhenti total.
Dengan pemahaman ini, dua keputusan desain menjadi jelas. Saat menulis method yang menghadapi kegagalan lingkungan yang pemanggil perlu ketahui — I/O, jaringan, basis data — pilih atau buat checked exception dan deklarasikan lewat throws. Saat kegagalan menandakan kontrak method dilanggar — argumen mustahil atau keadaan internal tidak konsisten — lempar unchecked exception tanpa membebani tanda tangan. Untuk mengoperasikan kedua sisi itu secara aktif, kita perlu dua kata kunci yang selama ini baru muncul sebagai keterangan: throw untuk melempar dan throws untuk menyatakan — topik Chapter berikutnya.
Referensi #
- Class RuntimeException pada API java.lang
- Class Exception pada API java.lang
- Oracle: Checked dan Unchecked Exceptions di dev.java
- Java Language Specification bab 11: Exceptions
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
CheckedDitangani.java
Unduh berkasimport java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class CheckedDitangani {
static String bacaBarisPertama() throws IOException {
try (BufferedReader pembaca = new BufferedReader(new StringReader("satu\ndua"))) {
return pembaca.readLine();
}
}
public static void main(String[] args) {
try {
System.out.println("baris: " + bacaBarisPertama());
} catch (IOException e) {
System.out.println("gagal membaca: " + e.getClass().getSimpleName());
}
}
}
TanpaMenangani.java
Unduh berkasimport java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class TanpaMenangani {
static String bacaBarisPertama() throws IOException {
try (BufferedReader pembaca = new BufferedReader(new StringReader("satu\ndua"))) {
return pembaca.readLine();
}
}
public static void main(String[] args) {
System.out.println("baris: " + bacaBarisPertama());
}
}
UncheckedBebas.java
Unduh berkaspublic class UncheckedBebas {
static int kecepatan(int nilai) {
if (nilai < 0) {
throw new IllegalArgumentException("kecepatan tidak boleh negatif: " + nilai);
}
return nilai;
}
public static void main(String[] args) {
System.out.println("kecepatan: " + kecepatan(60));
try {
System.out.println("kecepatan: " + kecepatan(-10));
} catch (IllegalArgumentException e) {
System.out.println("ditolak: " + e.getMessage());
}
}
}