Tuesday, March 27, 2018

8. Penanganan Eksepsi Bagian Dua



Jika Anda mencoba mengkompilasi program ini, Anda akan menerima pesan error yang menyatakan bahwa statemen catch kedua tidak dapat diraih kendali program karena eksepsi telah ditangkap. Karena ArithmeticException merupakan sebuah subkelas dari Exception, klausa catch pertama akan menangani semua error berbasis-Exception, termasuk ArithmeticException. Ini berarti bahwa klausa catch kedua tidak akan pernah dieksekusi. Untuk mengatasi permasalahan ini, Anda bisa membali urutan klausa catch seperti ini:

/* 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(ArithmeticException e) { // ERROR
         System.out.println("Ini menangkap eksepsi aritmatika.");
      }
      catch(Exception e) {
          System.out.println("Menangkap eksepsi Exception.");
      }
   }
}


Statemen try Bersarang
Statemen try dapat dibuat bersarang. Jadi, sebuah statemen try berada di dalam blok try lainnya. Setiap kali sebuah statemen try dimasuki oleh kendali program, konteks dari eksepsi itu ditempatkan ke tumpukan. Jika sebuah statemen try sebelah dalam tidak memiliki handler catch untuk eksepsi tertentu, tumpukan akan dibuka dan handler catch dari statemen try selanjutnya akan diperiksa kecocokan eksepsinya. Ini berlanjut sampai salah satu klausa catch menangkap eksepsi yang dilempar atau sampai semua statemen try bersarang selesai dieksekusi. Jika tidak ada klausa catch yang menangkap eksepsi yang dilempar, maka sistem run-time Java akan menangani eksepsi tersebut. Berikut adalah sebuah contoh yang menggunakan statemen-statemen try bersarang:

// Sebuah contoh statemen try bersarang.
public class TryBersarang {
   public static void main(String args[]) {
      try {
         int a = args.length;
  
         /* Jika argumen perintah-baris tidak disediakan,
          * statemen berikut akan menghasilkan eksepsi
          * pembagian-nol. */
         int b = 42 / a;
         System.out.println("a = " + a);
  
         try { // blok try bersarang
            /* Jika satu argumen perintah-baris digunakan,
             * maka eksepsi pembagian-nol akan dihasilkan
             * oleh kode berikut. */
            if(a==1) a = a/(a-a); // pembagian nol
               /* Jika dua argumen perintah-baris digunakan,
                * maka eksepsi out-of-bounds akan dihasilkan
                * oleh kode berikut. */
               if(a==2) {
                  int c[] = { 1 };
                  c[42] = 99; // eksepsi out-of-bounds akan dihasilkan
               }
         } catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("Indeks array di luar batas: " + e);
         }
      } catch(ArithmeticException e) {
       System.out.println("Pembagian oleh 0: " + e);
      }
   }
}

Seperti yang dapat Anda lihat, program ini menyarangkan satu blok try di dalam blok try lain. Program ini bekerja sebagai berikut. Ketika Anda mengeksekusi program tanpa menggunakan argumen perintah-baris, eksepsi pembagian-nol akan dibangkitkan oleh blok try sebelah luar. Eksekusi atas program dengan satu argumen perintah-baris akan menghasilkan eksepsi pembagian-nol dari dalam blok try bersarang (sebelah dalam). Karena blok try sebelah dalam tidak menangkap eksepsi ini, eksepsi tersebut dilewatkan kepada blok try sebelah luar, yang ditangkap dan ditangani. Jika Anda mengeksekusi program dengan dua argumen perintah-baris, maka eksepsi batas array akan dibangkitkan dari dalam blok try sebelah dalam. Berikut adalah contoh bagaimana menjalankan program untuk mengilustrasikan tiap kasus yang didiskusikan ini:

C:\>java TryBersarang
Pembagian oleh 0: java.lang.ArithmeticException: / by zero

C:\>java TryBersarang Satu
a = 1
Pembagian oleh 0: java.lang.ArithmeticException: / by zero

C:\>java TryBersarang Satu Dua
a = 2
Indeks array di luar batas:
java.lang.ArrayIndexOutOfBoundsException:42

Penyarangan statemen try dapat terjadi dengan cara yang sedikit kurang nyata ketika melibatkan pemanggilan metode. Sebagai contoh, Anda dapat membungkus sebuah pemanggilan terhadap metode di dalam sebuah blok try. Di dalam metode tersebut terdapat statemen try lain. Pada kasus ini, statemen try di dalam metode masih tetap bersarang di dalam blok try sebelah luar, yang memanggil metode. Berikut adalah program sebelumnya yang ditulis ulang sehingga blok try bersarang dipindahkan ke dalam metode tryBersarang():

/* Statemen-statemen try dapat secara implisit dibuat
 * bersarang melalui pemanggilan metode. */

public class TryBersarangMetode {
   static void tryBersarang(int a) {
      try { // blok try bersarang
          /* Jika satu argumen perintah-baris digunakan,
           * maka eksepsi pembagian-nol akan dihasilkan
           * oleh kode berikut. */
         if(a==1) a = a/(a-a); // pembagian nol
         
         /* Jika dua argumen perintah-baris digunakan,
          * maka eksepsi out-of-bounds akan dihasilkan
          * oleh kode berikut. */
         if(a==2) {
            int c[] = { 1 };
            c[42] = 99; // eksepsi out-of-bounds akan dihasilkan
         }
      } catch(ArrayIndexOutOfBoundsException e) {
         System.out.println("Indeks array di luar batas: " + e);
      }
   }
 
   public static void main(String args[]) {
      try {
         int a = args.length;
  
         /* Jika argumen perintah-baris tidak disediakan,
          * statemen berikut akan menghasilkan eksepsi
          * pembagian-nol. */
         int b = 42 / a;
  
         System.out.println("a = " + a);

         tryBersarang(a);
      } catch(ArithmeticException e) {
         System.out.println("Divide by 0: " + e);
      }
   }
}

Keluaran program ini identik dengan keluaran dari program sebelumnya.


Klausa throw
Sejauh ini, Anda hanya menangkap eksepsi yang dilempar oleh sistem run-time Java. Namun, program Anda bisa melemparkan eksepsi secara eksplisit, menggunakan statemen throw. Bentuk umum dari throw ditunjukkan di sini:

throw objekThrowable;

Di sini, objekThrowable harus merupakan sebuah objek bertipe Throwable atau subkelas dari Throwable. Tipe data primitif, seperti int atau char, maupun kelas-kelas tak-Throwable, seperti String dan Object, tidak dapat dipakai sebagai eksepsi. Ada dua cara bagaimana Anda bisa memperoleh objek Throwable: menggunakan sebuah parameter pada klausa catch atau menciptakannya menggunakan operator new.

Aliran eksekusi berhenti setelah statemen throw dieksekusi; setiap statemen setelahnya tidak akan dieksekusi. Blok try penutup terdekat akan diinspeksi untuk melihat apakah ia memiliki statemen catch yang cocok dengan tipe eksepsi. Jika cocok, kendali program akan dialihkan kepada klausa catch tersebut. Jika tidak, maka statemen try penutup berikutnya akan diperiksa, dan seterusnya. Jika tidak ada klausa catch yang cocok dengan tipe eksepsi yang dilempat, maka handler eksepsi default akan menghentikan program dan menampilkan jejak tumpukan pemanggilan.

Berikut adalah sebuah program sederhana yang menciptakan dan melemparkan sebuah eksepsi. Handler yang menangkap eksepsi melemparkan kembali eksepsi tersebut ke handler sebelar luar.

// Mendemonstrasikan throw

public class DemoThrow {
   static void proses() {
      try {
         throw new NullPointerException("Demo");
      } catch(NullPointerException e) {
         System.out.println("Menangkap eksepsi di dalam proses().");
         throw e; // melemparkan kembali eksepsi
      }
   }
  
   public static void main(String args[]) {
      try {
       proses();
      } catch(NullPointerException e) {
         System.out.println("Menangkap kembali eksepsi: " + e);
      }
   }
}

Ketika dijalankan, program tersebut menghasilkan keluaran:

Menangkap eksepsi di dalam proses().
Menangkap kembali eksepsi: java.lang.NullPointerException: Demo

Program ini mendapatkan dua kesempatan dalam menangani error. Pertama, main() menciptakan sebuah konteks eksepsi dan kemudian memanggil proses(). Metode proses() kemudian menciptakan konteks penanganan-eksepsi yang lain dan dan melemparkan objek baru bertipe NullPointerException, yang ditangkap pada baris berikutnya. Eksepsi itu kemudian dilemparkan kembali.

Program juga mengilustrasikan bagaimana menciptakan salah satu objek eksepsi standar dalam Java. Perhatikan khusus pada baris ini:

throw new NullPointerException("Demo");

Di sini, new dipakai untuk menciptakan sebuah objek dari kelas NullPointerException. Banyak eksepsi pustaka Java memiliki sedikitnya dua konstruktor: satu konstruktor tanpa parameter dan konstruktor lain yang mengambil satu parameter string. Ketika bentuk kedua digunakan, argumen menetapkan sebuah string yang menjelaskan eksepsi. String ini kemudian ditampilkan ketika objek dipakai sebagai argumen kepada print() atau println().


Klausa throws
Jika sebuah metode menyebabkan sebuah eksepsi yang tidak bisa ditanganinya, metode itu harus menyatakan watak ini sehingga pemanggil metode tersebut dapat waspada terhadap eksepsi tersebut. Anda melakukan hal ini dengan mencantumkan klausa throws pada deklarasi metode. Klausa throws mencantumkan semua tipe eksepsi yang bisa dilempar oleh metode tertentu. Ini penting untuk semua eksepsi, kecuali eksepsi yang bertipe Error atau RuntimeException, atau semua subkelasnya. Semua eksepsi lain yang dilempar oleh sebuah metode harus dideklarasikan pada klausa throws. Jika itu tidak dilakukan, error kompilasi akan terjadi.

Ini merupakan bentuk umum dari sebuah deklarasi metode yang mencantumkan klausa throws:

tipe-data nama-metode(daftar-param) throws daftar-eksepsi
{
   // tubuh metode
}

Di sini, daftar-eksepsi merupakan daftar yang memuat eksepsi-eksepsi, satu eksepsi dari eksepsi lain dipisahkan dengan koma, yang dapat dilempar oleh sebuah metode.

Berikut adalah sebuah contoh dari program yang tidak bisa dikompilasi. Program tersebut mencoba melemparkan sebuah eksepsi yang tidak ditangkapnya.

// Program ini memuat error, jadi tidak bisa dikompilasi
public class DemoThrows {
   static void lemparSatu() {
      System.out.println("Di dalam lemparSatu.");
         throw new IllegalAccessException("demo");
   }
  
   public static void main(String args[]) {
    lemparSatu();
   }
}

Untuk membuat program di atas agar dapat dikompilasi, Anda perlu membuat dua perubahan. Pertama, Anda perlu mendeklarasikan bahwa lemparSatu() agar melemparkan eksepsi IllegalAccessException. Kedua, main() harus mendefinisikan sebuah statemen try/catch yang menangkap eksepsi ini. Berikut adalah versi yang benar dari program di atas:

// Ini baru benar
public class DemoThrows {
   static void lemparSatu() throws IllegalAccessException {
      System.out.println("Di dalam lemparSatu.");
         throw new IllegalAccessException("demo");
   }
  
   public static void main(String args[]) {
      try {
         lemparSatu();
      } catch (IllegalAccessException e) {
         System.out.println("Menangkap " + e);
      }
   }
}

Ketika dijalankan, program tersebut menghasilkan keluaran:

Di dalam lemparSatu.
Menangkap java.lang.IllegalAccessException: demo


Klausa finally
Ketika eksepsi dilempar, eksekusi pada sebuah metode mengalami perubahan mendadak karena alur eksekusinya berubah. Memang tergantung bagaimana metode dituliskan kodenya, tetapi tetap ada kemungkinan terjadi eksepsi yang menyebabkan alur eksekusi keluar dari metode secara premature. Ini bisa jadi masalah pada beberapa metode. Misalnya, jika sebuah metode membuka file pada awal dieksekusi dan menutup file tersebut di akhir metode sebelum eksekusi terhadap metode itu berakhir, maka Anda tentu tidak mau kode yang bertujuan menutup file tersebut dilompati oleh mekanisme penanangan-eksepsi. Katakunci finally dirancang untuk menyelesaikan permasalahan darurat seperti ini.

Klausa finally menciptakan sebuah blok kode yang akan selalu dieksekusi setelah blok try/catch selesai dieksekusi dan sebelum kode yang ada setelah blok try/catch dieksekusi. Blok finally tetap akan dieksekusi, tanpa memandang ada tidaknya eksepsi dilemparkan. Jika sebuah eksepsi dilemparkan, blok finally akan dieksekusi bahkan jika tidak ada statemen catch yang cocok dengan eksepsi yang dilempar. Setiap saat alur eksekusi dari sebuah metode akan kembali kepada pemanggil metode dari dalam blok try/catch, baik melalui eksepsi yang tidak ditangkap maupun lewat statemen return eksplisit, klausa finally juga selalu dieksekusi tepat sebelum alur eksekusi meninggalkan metode tersebut. Ini berguna ketika dipakai untuk melakukan operasi penutupan file dan membebaskan semua sumberdata yang telah dialokasikan di awal sebuah metode. Klausa finally bersifat opsional. Namun, setiap statemen try mensyaratkan satu klausa catch atau satu klausa finally.

Berikut adalah sebuah program contoh yang menunjukkan tiga metode yang eksekusinya berakhir dengan sejumlah cara, tidak ada yang tidak mengeksekusi klausa finally-nya masing-masing:

// Mendemonstrasikan klausa finally
public class DemoFinally {
   // Melempar sebuah eksepsi dari metode.
   static void prosesA() {
      try {
         System.out.println("Di dalam prosesA");
            throw new RuntimeException("demo");
      } finally {
         System.out.println("Klausa finally dari prosesA");
      }
   }
 
   // Keluar dari dalam sebuah blok try.
   static void prosesB() {
      try {
         System.out.println("Di dalam prosesB");
         return;
      } finally {
         System.out.println("Klausa finally dari prosesB");
      }
   }
 
   // Mengeksekusi blok try secara normal.
   static void prosesC() {
      try {
         System.out.println("Di dalam prosesC");
      } finally {
         System.out.println("Klausa finally dari prosesC");
      }
   }
 
   public static void main(String args[]) {
      try {
         prosesA();
      } catch (Exception e) {
         System.out.println("Eksepsi ditangkap");
      }
 
      prosesB();
      prosesC();
   }
}

Ketika program dijalankan, dihasilkan keluaran berikut:

Di dalam prosesA
Klausa finally dari prosesA
Eksepsi ditangkap
Di dalam prosesB
Klausa finally dari prosesB
Di dalam prosesC
Klausa finally dari prosesC

Pada contoh ini, prosesA() secara prematur selesai dieksekusi karena blok try dengan melemparkan sebuah eksepsi. Klausa finally dieksekusi sesaat sebelum alur eksekusi keluar dari metode tersebut. Eksekusi terhadap statemen try dari metode prosesB() berakhir melalui statemen return. Klausa finally dieksekusi sesaat sebelum alur eksekusi keluar dari metode tersebut. Pada metode prosesC(), statemen try dieksekusi secara normal, tanpa error. Namun, blok finally masih tetap dieksekusi.



No comments: