Checked vs Unchecked

Menimbang kapan checked exception membantu dan kapan unchecked lebih jujur untuk kode modern.

Daftar isi

Kompilator Java memperlakukan exception dengan dua aturan yang berbeda, dan perbedaan itulah yang memisahkan checked dari unchecked. Checked exception wajib ditangani atau dideklarasikan sebelum kode bisa dikompilasi, sedangkan unchecked exception sama sekali tidak menuntut apa pun. Topik ini mengurai aturan kedua keluarga tersebut, menunjukkan perilakunya lewat contoh yang bisa dijalankan, lalu menimbang kapan masing-masing menjadi pilihan yang tepat dalam desain API.

Aturan kompilator untuk checked exception#

Checked exception adalah turunan Exception yang tidak melalui RuntimeException, misalnya IOException dan ClassNotFoundException. Untuk pemanggilan metode yang melempar checked exception, kompilator memberi dua jalan, yaitu menangkapnya dengan try-catch atau meneruskan kewajibannya lewat deklarasi throws. Prinsip ini sering disebut catch or specify, yakni tangkap atau nyatakan. Contoh berikut memakai Class.forName, metode pustaka yang memuat kelas berdasarkan namanya dan melempar ClassNotFoundException bila kelasnya tidak ada.

java DemoChecked.java
public class DemoChecked {

    public static void main(String[] args) {
        try {
            Class<?> kelas = Class.forName("java.lang.String");
            System.out.println("Kelas ditemukan: " + kelas.getName());
            Class.forName("java.lang.TidakAda");
            System.out.println("Baris ini tidak pernah dieksekusi");
        } catch (ClassNotFoundException e) {
            System.out.println("Pencarian gagal: " + e.getMessage());
        }
        System.out.println("Program berlanjut");
    }
}
text Keluaran
Kelas ditemukan: java.lang.String
Pencarian gagal: java.lang.TidakAda
Program berlanjut

Menjalankannya dari terminal cukup dengan satu perintah berikut.

bash
# berada di folder tempat DemoChecked.java disimpan
java DemoChecked.java
text Keluaran
Kelas ditemukan: java.lang.String
Pencarian gagal: java.lang.TidakAda
Program berlanjut

Pemanggilan pertama berhasil menemukan kelas String, sedangkan pemanggilan kedua melempar ClassNotFoundException karena java.lang.TidakAda memang tidak ada. Eksekusi langsung melompat ke blok catch, sehingga cetakan yang menyusulnya tidak pernah muncul. Tanpa blok catch, kode berikut bahkan tidak akan lolos kompilasi.

java Contoh: ditolak karena checked exception tidak ditangani
// tidak-dijalankan
static Class<?> bacaKelas(String nama) {
    return Class.forName(nama);
}

Pesan kesalahannya menjelaskan dua jalan yang tersedia, yaitu unreported exception ClassNotFoundException, must be caught or declared to be thrown. Metode bacaKelas harus menangkap ClassNotFoundException sendiri, atau menyatakan throws ClassNotFoundException agar kewajiban itu pindah ke pemanggilnya. Class.forName dipilih sebagai contoh karena kelas yang dimuat bergantung pada lingkungan eksekusi, yaitu classpath dan pustaka yang terpasang. Kompilator tidak mungkin mengetahui keberadaan kelas tersebut saat kompilasi, sehingga kegagalan pemuatannya memang layak dilaporkan sebagai checked exception yang harus diantisipasi pemanggil. Keluarga I/O mengikuti pola yang sama, karena kegagalan baca tulis berkas ditentukan oleh kondisi perangkat dan berkas yang tidak bisa dipastikan kompilator.

Keluarga unchecked dan kebebasannya#

Unchecked exception mencakup RuntimeException beserta seluruh turunannya, ditambah Error yang memang tidak untuk ditangani. Anggotanya yang paling sering ditemui adalah NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException, IllegalArgumentException, dan IllegalStateException. Kompilator tidak menuntut penanganan apa pun untuk keluarga ini, baik pada pelempar maupun pada pemanggilnya.

java DemoUnchecked.java
public class DemoUnchecked {

    public static void main(String[] args) {
        try {
            System.out.println(bagi(10, 2));
            System.out.println(bagi(10, 0));
        } catch (IllegalArgumentException e) {
            System.out.println("Ditolak: " + e.getMessage());
        }
        System.out.println("Tidak ada deklarasi throws di mana pun");
    }

    static int bagi(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("pembagi tidak boleh nol");
        }
        return a / b;
    }
}
text Keluaran
5
Ditolak: pembagi tidak boleh nol
Tidak ada deklarasi throws di mana pun

Metode bagi melempar IllegalArgumentException tanpa deklarasi throws apa pun, dan kompilator menerimanya begitu saja. Pemanggil pun bebas menangkap atau mengabaikannya. Tangkapan di atas dipilih karena program ingin melaporkan penolakan tersebut dengan rapi, bukan karena kompilator menuntutnya.

Perhatikan bahwa Error ikut keluarga unchecked tetapi berada di luar perdebatan desain, karena tipe ini tidak dimaksudkan untuk dilempar oleh kode aplikasi. Keputusan checked atau unchecked pada praktiknya adalah keputusan antara turunan Exception lain dan turunan RuntimeException. Anggota yang paling umum seperti IllegalArgumentException dipakai pustaka standar untuk menolak argumen yang tidak sah, misalnya List.of yang menolak elemen null.

Menimbang kapan masing-masing membantu#

Pilihan antara checked dan unchecked adalah keputusan desain tentang siapa yang harus mengantisipasi kegagalan. Tabel berikut merangkum perbedaan dasarnya.

AspekCheckedUnchecked
Kelas dasarnyaException selain RuntimeExceptionRuntimeException dan Error
Tuntutan kompilatorwajib ditangkap atau dideklarasikantidak ada
Kegagalan yang dimodelkankondisi eksternal yang mungkin pulihkesalahan pemrograman atau data yang tidak sah
ContohIOException, SQLExceptionNullPointerException, IllegalArgumentException

Checked exception unggul ketika kegagalan adalah bagian wajar dari pemakaian fitur dan pemanggil memang bisa berbuat sesuatu. Membaca berkas yang alamatnya diketik pengguna bisa gagal, dan pemanggil adalah pihak yang tepat untuk memutuskan apakah mengambil berkas cadangan atau menampilkan pesan. Kewajiban dari kompilator memaksa setiap pemanggil memikirkan kemungkinan itu, sehingga kasusnya tidak terlewat.

Kelemahannya muncul saat rantai pemanggilan panjang. Checked exception yang dideklarasikan di lapisan bawah merambat ke seluruh lapisan di atasnya, memaksa tiap metode menambahkan throws atau membungkusnya, kendati lapisan tengah tidak tahu apa yang harus dilakukan. Biaya inilah yang membuat banyak pustaka modern memilih unchecked exception. Untuk kegagalan yang menandakan bug, seperti argumen null atau argumen di luar rentang, unchecked lebih jujur karena pemanggil yang salah tidak pantas dituntut menangkap kesalahannya sendiri, melainkan memperbaiki kodenya.

Apa pun pilihannya, kegagalan yang mungkin terlempar sebaiknya didokumentasikan. Javadoc menerima tag @throws untuk menjelaskan kondisi yang memicu pelemparan, dan tag ini tetap penting untuk unchecked exception yang tidak terlihat pada tanda tangan metode. Dokumentasi semacam ini menggantikan jaminan kompilator yang hanya melekat pada checked exception.

Kesalahan umum#

  • Menangkap checked exception hanya agar kompilasi lolos, lalu membiarkan blok catch kosong. Kegagalan tetap terjadi tanpa jejak. Tangkap untuk pulih, deklarasikan untuk diteruskan, atau jangan paksa diri menanganinya di tempat yang salah.
  • Menulis throws Exception pada metode. Semua pemanggil dipaksa menangani Exception yang umum, dan informasi tipe kegagalannya hilang. Deklarasikan tipe yang paling spesifik.
  • Mengira unchecked exception berarti tidak perlu dipikirkan sama sekali. Kompilator memang diam, tetapi NullPointerException yang tidak dijaga tetap menghentikan program saat berjalan.
  • Memakai checked exception untuk kesalahan pemrograman, misalnya argumen null. Pemanggil tidak dapat memulihkan bug, sehingga kewajiban menangkapnya hanya menambah kebisingan.

Ringkasan#

  • Checked exception adalah turunan Exception selain RuntimeException dan wajib ditangkap atau dideklarasikan lewat throws.
  • Unchecked exception mencakup RuntimeException beserta turunannya dan tidak menuntut penanganan dari kompilator.
  • Checked cocok untuk kegagalan eksternal yang pemanggil dapat pulihkan, sedangkan unchecked cocok untuk kesalahan pemrograman dan data yang tidak sah.
  • Banyak pustaka modern condong ke unchecked exception, dengan syarat penyebab kegagalan tetap dibawa saat pembungkusan.

Referensi#