Tuesday, March 27, 2018

8. Penanganan Eksepsi Bagian Satu



Bab ini akan menyajikan mekanisme penanganan-eksepsi Java. Eksepsi adalah sebuah kondisi abnormal yang terjadi ketika program dijalankan. Dengan kata lain, eksepsi merupakan sebuah error run-time. Pada bahasa-bahasa komputer yang tidak mendukung penanganan eksepsi, error harus diperiksa dan ditangani secara manual. Pendekatan ini sangat melelahkan dan bermasalah. Penanganan eksepsi pada Java dipakai untuk menghindari permasalahan ini dengan mengintegrasikan pengelolaan error run-time ke dalam dunia berorientasi-objek.


Dasar-Dasar Penanganan Eksepsi
Eksepsi dalam Java adalah sebuah objek yang menjelaskan kejadian luar biasa (error) yang terjadi pada sebuah potongan kode. Ketika sebuah kejadian luar biasa terjadi, sebuah objek yang merepresentasikan eksepsi akan diciptakan dan dilemparkan di dalam metode yang menyebabkan error tersebut. Metode itu dapat memilih untuk menangani sendiri eksepsi itu atau melemparkannya kepada metode lain. Pada titik tertentu, eksepsi akan ditangkap dan diproses. Eksepsi dibangkitkan oleh sistem run-time Java atau bisa juga dihasilkan oleh kode Anda sendiri. Eksepsi yang dilemparkan oleh Java berkaitan dengan error-error fundamental yang melanggar aturan-aturan dari bahasa Java atau yang melanggar batasan-batasan dari lingkungan eksekusi Java. Eksepsi yang dibangkitkan secara manual umumnya dipakai untuk melaporkan kondisi error tertentu kepada pemanggil dari sebuah metode.

Penanganan eksepsi Java dikelola menggunakan lima katakunci: try, catch, throw, throws, dan finally. Secara singkat, berikut adalah bagaimana kelimanya beroperasi. Statemen-statemen program yang ingin dimonitor eksepsinya dimuat ke dalam sebuah blok try. Jika sebuah eksepsi terjadi di dalam blok try tersebut, eksepsi itu akan dilemparkan. Kode Anda kemudian dapat menangkap eksepsi ini (menggunakan catch) dan menanganinya dengan cara rasional tertentu. Eksepsi yang dibangkitkan oleh sistem Java secara otomatis dilempar oleh sistem run-time Java. Untuk melemparkan eksepsi secara manual, Anda bisa menggunakan klausa throw. Setiap eksepsi yang dilemparkan keluar dari sebuah metode harus ditetapkan menggunakan klausa throws. Setiap kode yang harus dieksekusi setelah blok try dieksekusi harus ditempatkan ke dalam blok finally.

Berikut adalah bentuk umum dari blok penanganan-eksepsi:

try {
   // blok kode untuk memonitor error
}

catch (TipeEksepsi1 obEks1) {
   // kode untuk menangani eksepsi TipeEksepsi1
}

catch (TipeEksepsi2 obEks2) {
   // kode untuk menangani eksepsi TipeEksepsi2
}
// ...

finally {
   // blok kode yang akan dieksekusi setelah blok try berakhir
}

Di sini, TipeEksepsi adalah tipe eksepsi yang terjadi.


Tipe-Tipe Eksepsi
Semua tipe eksepsi adalah subkelas dari kelas pustaka Throwable. Jadi, kelas Throwable berada di bagian atas pada hierarki pewarisan kelas eksepsi. Di bawah kelas Throwable adalah dua subkelas yang membagi eksepsi menjadi dua cabang. Satu cabang diwarisi oleh kelas Exception. Kelas ini dipakai untuk kondisi-kondisi luar biasa yang harus ditangkap oleh program user. Ada satu subkelas penting dari kelas ini, dinamakan dengan RuntimeException. Eksepsi dengan tipe ini secara otomatis didefinisikan untuk program-program yang Anda tulis mencakup pembagian oleh nol dan pengindeksan array tak-valid.

Cabang lain diwarisi oleh kelas Error, yang mendefinisikan eksepsi-eksepsi yang tidak diduga oleh kondisi normal pada program Anda. Eksepsi bertipe Error dipakai oleh sistem run-time Java untuk mengindikasikan bahwa error yang terjadi berkaitan dengan lingkungan run-time itu sendiri. Bab ini tidak membahas eksepsi tipe ini, karena umumnya eksepsi ini berkaitan dengan kegagalan dramatis dan katastrofik yang tidak bisa ditangani oleh program sederhana.

Hierarki pewarisan eksepsi level-atas ditunjukkan di sini:



Eksepsi Yang Tidak Ditangkap
Sebelum Anda belajar bagaimana menangani eksepsi pada program Anda, sebaiknya Anda mengetahui lebih dahulu apa yang akan terjadi ketika Anda tidak menanganinya. Program pendek ini memuat sebuah ekspresi yang secara sengaja menyebabkan error pembagian oleh nol:

class EksepsiTakDitangkap {
   public static void main(String args[]) {
      int d = 0;
      int a = 42 / d;
   }
}

Ketika sistem run-time Java mendeteksi ekspresi pembagian oleh nol tersebut, ia menciptakan sebuah objek eksepsi baru dan kemudian melemparkan eksepsi ini. Ini menyebabkan eksekusi terhadap EksepsiTakDitangkap terhenti, karena begitu sebuah eksepsi dilemparkan, ia harus ditangkap oleh kode tertentu yang akan menanganinya.

Pada contoh ini, Anda tidak menyediakan handler untuk menangani eksepsi yang dilempar, jadi eksepsi ditangkap oleh handler default yang disediakan oleh sistem run-time Java. Handler default tersebut menampilkan sebuah string yang menjelaskan eksepsi, menampilkan jejak tumpukan dari titik dimana eksepsi terjadi, dan menghentikan program.

Berikut adalah eksepsi yang dihasilkan ketika program ini dieksekusi:

Exception in thread "main" java.lang.ArithmeticException: / by zero
 at EksepsiTakDitangkap.main(EksepsiTakDitangkap.java:4)

Perhatikan bagaimana nama kelas, EkspepsiTakDitangkap; nama metode, main; nama file EksepsiTakDitangkap.java; dan nomor baris kode, 4, semua dicantumkan pada jejak tumpukan sederhana tersebut. Selain itu, perhatikan bahwa tipe eksepsi yang dilempar adalah sebuah subkelas dari Exception yang dinamakan dengan ArithmeticException, yang secara spesifik menjelaskan jenis error yang terjadi. Nanti pada bab ini akan ditunjukkan bahwa Java memiliki sejumlah tipe eksepsi pustaka.

Jejak tumpukan selalu menunjukkan urutan pemanggilan metode yang mengarah ke error. Sebagai contoh, berikut adalah versi lain dari program sebelumnya yang menyebabkan error yang sama tetapi ditempatkan di dalam sebuah metode yang terpisah dari main():

public class EksepsiTakDitangkap2 {
   static void subrutin() {
      int d = 0;
      int a = 42 / d;
   }
   
   public static void main(String args[]) {
      EksepsiTakDitangkap2.subrutin();
   }
}

Jejak tumpukan pemanggilan dari handler eksepsi default menunjukkan bagaimana keseluruhan tumpukan pemanggilan ditampilkan:

Exception in thread "main" java.lang.ArithmeticException: / by zero
 at EksepsiTakDitangkap2.subrutin(EksepsiTakDitangkap2.java:4)
 at EksepsiTakDitangkap2.main(EksepsiTakDitangkap2.java:7)

Seperti yang dapat dilihat, bagian terbawah dari tumpukan adalah baris 7 pada metode main(), yang memanggil subrutin(), yang menyebabkan eksepsi pada baris 4. Tumpukan pemanggilan ini berguna untuk debugging, karena ia menyajikan setiap langkah runtun yang mengarah ke error.


Menggunakan try dan catch
Meskipun handler eksepsi default yang disediakan oleh sistem run-time Java berguna pada saat debugging, Anda biasanya ingin menangani sendiri eksepsi yang terjadi. Ada dua keuntungan bila menangani sendiri eksepsi. Pertama, Anda akan bisa belajar bagaimana menangani error. Kedua, hal itu bisa menghindarkan program dari penghentian otomatis. Kebanyakan user menjadi bingung jika programnya berhenti mendadak dan menampilkan sebuah jejak tumpukan pemanggilan ketika error terjadi. Untunglah, hal itu cukup mudah diatasi.

Untuk mengawal dan menangani error run-time, Anda perlu membungkus kode yang ingin Anda monitor di dalam sebuah blok try. Tepat setelah blok try, Anda perlu mencantumkan sebuah klausa catch yang menetapkan tipe eksepsi yang akan ditangani. Untuk mengilustrasikan betapa mudahnya hal ini dapat dilakukan, program berikut menyajikan sebuah blok try dan blok catch yang memproses ArithmeticException yang dibangkitkan oleh pembagian oleh nol:

//Mendemonstrasikan bagaimana menangkap eksepsi
public class EksepsiDitangkap {
   public static void main(String args[]) {
      int d, a;
  
      try { // memonitor sebuah blok kode.
         d = 0;
         a = 42 / d;
  
         System.out.println("Ini tidak akan ditampilkan.");
      } catch (ArithmeticException e) { // menangkap eksepsi pembagian-oleh-nol
         System.out.println("Pembagian oleh nol.");
      }
 
      System.out.println("Setelah statemen catch.");
   }
}

Program ini menghasilkan keluaran berikut:

Pembagian oleh nol.
Setelah statemen catch.

Perhatikan bahwa pemanggilan terhadap println() di dalam blok try tidak pernah dieksekusi. Setelah eksepsi dilemparkan, kendali program keluar dari blok try ke dalam blok catch. Jadi baris “Ini tidak akan ditampilkan.” tidak akan ditampilkan. Setelah statemen catch dieksekusi, kendali program berlanjut ke baris selanjutnya pada program yang ada setelah keseluruhan mekanisme try/catch.

Statemen try dan catch membentuk satu unit logika. Skop dari klausa catch dibatasi hanya pada statemen-statemen yang ada pada blok statemen try terdekat. Statemen catch tidak bisa menangkap sebuah eksepsi yang dilemparkan oleh statemen try lain (kecuali pada kasus statemen try bersarang, yang sebentar lagi akan dijelaskan). Statemen-statemen yang diproteksi oleh try harus diapit dengan kurung kurawal. Anda tidak bisa menggunakan try pada satu statemen tunggal.

Tujuan dari klausa catch seharusnya adalah menangani dan menyelesaikan kondisi luar biasa yang terjadi dan eksekusi program berlanjut seperti layaknya error tidak pernah terjadi. Misalnya, pada program selanjutnya ini, setiap iterasi dari loop for mendapatkan dua integer acak. Kedua integer tersebut dibagi satu sama lain, dan hasilnya dipakai untuk membagi nilai 12345. Hasil akhirnya disimpan ke dalam a. Jika operasi pembagian menyebabkan error pembagian-nol, maka eksepsi akan ditangkap dan nilai a akan ditetapkan nol, dan program berlanjut.

// Menangani sebuah eksepsi dan melanjutkan eksekusi.
import java.util.Random;

public class MenanganiError {
   public static void main(String args[]) {
      int a=0, b=0, c=0;
      Random r = new Random();
  
      for(int i=0; i<100; i++) {
         try {
            b = r.nextInt();
            c = r.nextInt();
            a = 12345 / (b/c);
         } catch (ArithmeticException e) {
            System.out.println("Pembagian oleh nol.");
            a = 0; // menetapkan a bernilai nol
         }
  
         System.out.println("a: " + a);
      }
   }
}

Berikut adalah salah satu contoh keluaran program:

a: 3086
Pembagian oleh nol.
a: 0
Pembagian oleh nol.
a: 0
Pembagian oleh nol.
a: 0
Pembagian oleh nol.
a: 0
Pembagian oleh nol.
a: 0


Menampilkan Penjelasan Eksepsi
Kelas Throwable mendefinisikan ulang metode toString() (yang didefinisikan oleh kelas Object) sehingga menghasilkan sebuah string yang memuat penjelasan eksepsi. Anda bisa menampilkan penjelasan ini pada sebuah statemen println() dengan melewatkan eksepsi sebagai argumen. Misalnya, blok catch pada program sebelumnya dapat dituliskan ulang seperti ini:

catch (ArithmeticException e) {
   System.out.println("Eksepsi: " + e);
   a = 0; // menetapkan a bernilai nol
}

Ketika versi program ini dijalankan, setiap error pembagian-nol akan menampilkan pesan berikut:

Eksepsi: java.lang.ArithmeticException: / by zero


Klausa catch Jamak
Pada sejumlah kasus, lebih dari satu eksepsi dapat disebabkan oleh sepotong kode. Untuk menangani situasi seperti ini, Anda bisa memiliki dua atau lebih klausa catch, setiap klausa menangkap tipe eksepsi berbeda. Ketika sebuah eksepsi dilemparkan, tiap statemen catch diinspeksi secara berurutan, dan klausa catch pertama yang cocok dengan tipe eksepsi akan dieksekusi. Setelah satu statemen catch dieksekusi, statemen catch lainnya akan dilompati, dan kendali program berlanjut ke luar blok try/catch. Contoh berikut menangkap dua jenis eksepsi berbeda.

// Mendemonstrasikan statemen catch jamak.
public class CatchJamak {
   public static void main(String args[]) {
      try {
         int a = args.length;
  
         System.out.println("a = " + a);
  
         int b = 42 / a;
         int c[] = { 1 };
         c[42] = 99;
      } catch(ArithmeticException e) {
         System.out.println("Pembagian dengan 0: " + e);
      } catch(ArrayIndexOutOfBoundsException e) {
         System.out.println("Indeks array oob: " + e);
      }
  
      System.out.println("Setelah blok try/catch.");
   }
}

Program ini menyebabkan eksepsi pembagian-nol dilemparkan jika program dijalankan dengan tanpa argumen, karena a akan bernilai nol. Jika Anda memberikan argumen perintah-baris, itu berarti a akan bernilai lebih besar dari nol. Tetapi hal itu akan menyebabkan eksepsi ArrayIndexOutOfBoundsException dilemparkan, karena array int, c, memiliki panjang 1, tetapi program mencoba menugaskan sebuah nilai kepada c[42].

Berikut adalah keluaran yang dihasilkan program jika dijalankan dengan dua cara:

C:\>java CatchJamak
a = 0
Pembagian dengan 0: java.lang.ArithmeticException: / by zero
Setelah blok try/catch.

C:\>java CatchJamak UjiArg
a = 1
Indeks array oob: java.lang.ArrayIndexOutOfBoundsException: 42
Setelah blok try/catch.

Ketika Anda menggunakan sejumlah statemen catch, penting untuk diingat bahwa sub-subkelas eksepsi harus ditempatkan sebelum superkelasnya. Ini karena statemen catch yang menggunakan sebuah superkelas akan menangkap eksepsi bertipe superkelas itu dan semua subkelasnya. Jadi, eksepsi dari subkelas tidak akan pernah ditangkap jika ia ditempatkan setelah superkelasnya. Sebagai contoh, perhatikan program berikut:

/* Program ini memuat sebuah error.
 * Sebuah subkelas harus ditempatkan sebelum 
 * superkelasnya pada serangkaian statemen
 * catch. Jika tidak, kode tersebut
 * tidak akan pernah diraih oleh kendali
 * program dan hal itu menyebabkan error run-time.
*/

public class CatchSuperSub {
   public static void main(String args[]) {
      try {
         int a = 0;
         int b = 42 / a;
      } catch(Exception e) {
         System.out.println("Menangkap eksepsi Exception.");
      }
  
      /* Klausa catch ini tidak pernah diraih oleh kendali
       * program karena ArithmeticException adalah subkelas
       * dari kelas Exception. */
      catch(ArithmeticException e) { // ERROR
         System.out.println("Ini tidak pernah diraih kendali program.");
      }
   }
}



Selanjutnya  >>>

No comments: