Program yang kita tulis jarang berjalan mulus selamanya: berkas bisa hilang, pengguna bisa mengetik teks pada kolom angka, dan nilai pembagi bisa bernilai nol. Java menyediakan mekanisme bernama exception untuk melaporkan kegagalan semacam itu secara eksplisit, membawanya ke tempat yang tepat untuk ditangani, dan menjaga bagian program lain tetap terkendali. Pada Chapter ini kita bangun fondasinya: bagaimana alur program berubah saat exception muncul, apa yang sebenarnya dilakukan JVM di balik layar, dan mengapa cara lama berbasis kode balikan (return code) tidak lagi memadai untuk program yang besar.
Aliran Normal Program #
Dalam aliran normal, JVM mengeksekusi statement satu per satu dari atas ke bawah. Ketika sebuah method dipanggil, JVM menyimpan stack frame berisi posisi pemanggilan, menjalankan isi method, lalu kembali ke titik panggilan dan melanjutkan statement berikutnya. Mari kita amati alur tersebut pada program sederhana berikut:
public class AliranNormal {
static int hitungTotal(int harga, int jumlah) {
System.out.println("2. masuk hitungTotal");
int total = harga * jumlah;
System.out.println("3. keluar hitungTotal");
return total;
}
static void prosesPesanan() {
System.out.println("1. masuk prosesPesanan");
int total = hitungTotal(5000, 3);
System.out.println("4. total = " + total);
}
public static void main(String[] args) {
System.out.println("mulai program");
prosesPesanan();
System.out.println("program selesai");
}
}Output yang Dihasilkan:
mulai program
1. masuk prosesPesanan
2. masuk hitungTotal
3. keluar hitungTotal
4. total = 15000
program selesaiUrutan keluaran menegaskan perjalanan eksekusi: main memanggil prosesPesanan, method tersebut memanggil hitungTotal, hasil 15000 kembali ke pemanggil, dan eksekusi berlanjut sampai baris terakhir. Tidak ada satu pun statement yang terlewati. Kita akan kontraskan ketenteraman ini dengan aliran abnormal berikutnya.
Exception dan Stack Unwinding #
Exception adalah objek yang dilempar (thrown) ketika kondisi abnormal terdeteksi, dan setiap exception adalah turunan dari class Throwable di package java.lang. Exception membawa dua informasi penting: jenis kegagalannya (dari nama class-nya) dan pesan detail melalui method getMessage(). Ketika exception dilempar, JVM tidak melanjutkan statement berikutnya. Sebagai gantinya, JVM menjalankan stack unwinding: frame method dibongkar satu per satu dari titik kejadian naik ke atas — hitungRata, lalu prosesLaporan, lalu main — sampai ada pemanggil yang menangkap exception tersebut.
Program berikut sengaja membagi bilangan dengan nol:
public class AliranAbnormal {
static int hitungRata(int total, int jumlah) {
System.out.println("2. masuk hitungRata");
int rata = total / jumlah; // pembagian nol: JVM melempar ArithmeticException
System.out.println("3. keluar hitungRata");
return rata;
}
static void prosesLaporan() {
System.out.println("1. masuk prosesLaporan");
int rata = hitungRata(15000, 0);
System.out.println("4. rata = " + rata);
}
public static void main(String[] args) {
System.out.println("mulai program");
prosesLaporan();
System.out.println("program selesai");
}
}Tiga baris pertama tetap tercetak (mulai program sampai 2. masuk hitungRata), lalu program berhenti dengan exit code 1. JVM melaporkan kegagalan tersebut melalui default handler di stderr — baris pertamanya berbunyi Exception in thread "main" java.lang.ArithmeticException: / by zero, diikuti stack trace yang menyebut frame hitungRata, prosesLaporan, dan main beserta nomor barisnya. Perhatikan bahwa statement setelah titik kejadian tidak pernah berjalan: baris 3. keluar hitungRata, 4. rata = ..., dan program selesai semuanya terlewati karena unwinding melewati frame tersebut tanpa menjalankan sisanya.
Mengapa Return Code Tidak Cukup #
Sebelum ada exception, kegagalan dilaporkan lewat nilai balikan khusus. Method pencarian mengembalikan -1 saat tidak ketemu, fungsi sistem operasi klasik mengembalikan kode angka, dan setiap pemanggil harus mengingat makna tiap kode:
public class KodeBalik {
// Gaya lama: -1 berarti "tidak ditemukan", nilai lain berarti indeks sah.
static int cariIndeks(String[] data, String dicari) {
for (int i = 0; i < data.length; i++) {
if (data[i].equals(dicari)) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
String[] buah = {"mangga", "jeruk", "pisang"};
System.out.println("indeks jeruk : " + cariIndeks(buah, "jeruk"));
System.out.println("indeks durian : " + cariIndeks(buah, "durian"));
}
}Output yang Dihasilkan:
indeks jeruk : 1
indeks durian : -1Pendekatan ini bekerja, tetapi rapuh. Nilai -1 menempati saluran yang sama dengan hasil sah, maknanya hanya hidup di kesepakatan antara penulis method dan pemanggil, dan compiler sama sekali tidak memaksa pemanggil untuk memeriksanya. Lihat apa yang terjadi ketika pemeriksaan itu terlewat:
public class SentinelTerlupakan {
// -1 dipakai sebagai sinyal "kode tidak valid" lewat nilai balik.
static int hitungPotongan(int harga, String kode) {
if (kode == null) {
return -1;
}
return harga / 10;
}
public static void main(String[] args) {
int total = 250000;
int potongan = hitungPotongan(total, null);
// Pemanggil lupa memeriksa nilai -1:
System.out.println("Total setelah potongan: " + (total - potongan));
}
}Output yang Dihasilkan:
Total setelah potongan: 250001Program mencetak Total setelah potongan: 250001 — nilai yang salah, dihitung dari potongan -1, tanpa satu pun peringatan. Mari kita bedah kelemahannya secara ringkas:
- Baris 11 (
return -1;): sinyal kegagalan berbagi tipe dengan hasil sah. Pemanggil tidak bisa membedakan hasil dan error hanya dari tipe data. - Baris 18 (
int potongan = ...): compiler menerima pemanggilan tanpa pemeriksaan apa pun. Kesalahan ini baru mungkin terdeteksi saat pelanggan melihat total yang aneh. - Skala masalah: satu method dengan satu kode sinyal masih terkelola; tiga puluh method dengan kode sinyal berbeda-beda sudah menyulitkan. Exception memisahkan saluran hasil dari saluran kegagalan, memaksa penanganan pada tempat tertentu, dan membawa tipe serta pesan yang bisa diperiksa.
Dengan konsep ini di tangan, pertanyaan berikutnya natural: exception apa saja yang tersedia, dan bagaimana semuanya tersusun? Chapter berikutnya memetakan hierarki Throwable dari Error, Exception, hingga RuntimeException.
Referensi #
- Oracle: Exceptions di dev.java
- Class Throwable pada API java.lang
- Java Language Specification bab 11: Exceptions
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AliranAbnormal.java
Unduh berkaspublic class AliranAbnormal {
static int hitungRata(int total, int jumlah) {
System.out.println("2. masuk hitungRata");
int rata = total / jumlah; // pembagian nol: JVM melempar ArithmeticException
System.out.println("3. keluar hitungRata");
return rata;
}
static void prosesLaporan() {
System.out.println("1. masuk prosesLaporan");
int rata = hitungRata(15000, 0);
System.out.println("4. rata = " + rata);
}
public static void main(String[] args) {
System.out.println("mulai program");
prosesLaporan();
System.out.println("program selesai");
}
}
AliranNormal.java
Unduh berkaspublic class AliranNormal {
static int hitungTotal(int harga, int jumlah) {
System.out.println("2. masuk hitungTotal");
int total = harga * jumlah;
System.out.println("3. keluar hitungTotal");
return total;
}
static void prosesPesanan() {
System.out.println("1. masuk prosesPesanan");
int total = hitungTotal(5000, 3);
System.out.println("4. total = " + total);
}
public static void main(String[] args) {
System.out.println("mulai program");
prosesPesanan();
System.out.println("program selesai");
}
}
KodeBalik.java
Unduh berkaspublic class KodeBalik {
// Gaya lama: -1 berarti "tidak ditemukan", nilai lain berarti indeks sah.
static int cariIndeks(String[] data, String dicari) {
for (int i = 0; i < data.length; i++) {
if (data[i].equals(dicari)) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
String[] buah = {"mangga", "jeruk", "pisang"};
System.out.println("indeks jeruk : " + cariIndeks(buah, "jeruk"));
System.out.println("indeks durian : " + cariIndeks(buah, "durian"));
}
}
SentinelTerlupakan.java
Unduh berkaspublic class SentinelTerlupakan {
// -1 dipakai sebagai sinyal "kode tidak valid" lewat nilai balik.
static int hitungPotongan(int harga, String kode) {
if (kode == null) {
return -1;
}
return harga / 10;
}
public static void main(String[] args) {
int total = 250000;
int potongan = hitungPotongan(total, null);
// Pemanggil lupa memeriksa nilai -1:
System.out.println("Total setelah potongan: " + (total - potongan));
}
}