Thursday, March 22, 2018

13.Operasi I/O Bagian Dua



Meskipun program tersebut menutup aliran file setelah file selesai dibaca, ada cara alternatif yang seringkali lebih berguna. Alternatif ini adalah memanggil close() di dalam sebuah blok finally. Pada pendekatan ini, semua metode yang mengakses file dimuat di dalam sebuah blok try, dan blok finally dipakai untuk menutup file. Dengan cara ini, walau bagaimanapun blok try berhenti, file tetap ditutup. Pada program contoh tersebut, berikut adalah bagaimana blok try membaca file yang dapat dikode ulang menggunakan blok finally:

try {
   do {
      i = fin.read();
      if(i != -1) System.out.print((char) i);
   } while(i != -1);
} catch(IOException e) {
   System.out.println("Error Pembukaan File");
} finally {
   // Menutup file dengan cara keluar dari blok try.
   try {
      fin.close();
   } catch(IOException e) {
      System.out.println("Error Penutupan File");
   }
}

Meskipun tidak menjadi permasalahan pada kasus ini, salah satu keuntungan pada pendekatan ini secara umum adalah bahwa jika kode yang mengakses file berhenti karena eksepsi yang berkaitan dengan tak-I/O, file tersebut masih ditutup oleh blof finally.

Memang lebih mudah untuk membungkus porsi program yang membuka file dan mengakses file di dalam satu blok try (daripada menggunakan dua blok try terpisah) dan kemudian menggunakan sebuah blok finally untuk menutup file. Sebagai contoh, berikut adalah cara lain untuk menuliskan program TampilFile.

/*Menampilkan sebuah file teks.
* Untuk menggunakan program ini,
* tetapkan nama file yang akan Anda lihat
* Contoh, untuk melihat sebuah file dengan
* nama UJI.TXT, gunakan perintah baris
* java TampilFile UJI.TXT
* 
* Variasi ini membungkus kode yang membuka dan
* mengakses file di dalam satu blok try.
* File ditutup oleh blok finally.
*/
import java.io.*;

public class TampilFile2 {
   public static void main(String args[])
   {
      int i;
      FileInputStream fin=null;
 
      // Pertama, memastikan bahwa nama file telah diberikan.
      if(args.length != 1) {
         System.out.println("Nama file belum diberikan");
         return;
      }
 
      // Kode berikut membuka sebuah file, membaca karakter-karakter
      // sampai EOF ditemui, dan kemudian menutup file via
      // blok finally

      try {
         do {
            i = fin.read();
         if(i != -1) System.out.print((char) i);
         } while(i != -1);
      } catch(IOException e) {
         System.out.println("Error Pembukaan File");
      } finally {
         // Menutup file dengan cara keluar dari blok try.
         try {
            fin.close();
         } catch(IOException e) {
            System.out.println("Error Penutupan File");
         }
      }
   }
}

Pada pendekatan ini, perhatikan bahwa fin diinisialisasi dengan null. Kemudian, pada blok finally, file ditutup hanya jika fin tidak bernilai null. Ini dapat dilakukan karena fin akan benilai tak-null hanya jika file berhasil dibuka. Jadi, metode close() tidak dipanggil jika eksepsi terjadi ketika membuka file.

Anda dimungkinkan untuk membuat runtun try/catch pada contoh sebelumnya menjadi lebih ringkas. Karena FileNotFoundException adalah sebuah subkelas dari IOException, eksepsi tersebut tidak perlu ditangkap secara terpisah. Misalnya, berikut adalah sebuah runtun try/catch yang mengeliminasi penangkapan FileNotFoundException. Pada kasus ini, pesan eksepsi standar, yang menjelaskan error, akan ditampilkan.

FileNotFoundException. Pada kasus ini, pesan eksepsi standar, yang menjelaskan error, akan ditampilkan.

try {
   fin = new FileInputStream(args[0]);
   
   do {
      i = fin.read();

      if(i != -1) System.out.print((char) i);
   } while(i != -1);

} catch(IOException e) {
   System.out.println("Error I/O: " + e);

} finally {
   // Menutup file pada semua kasus.
   try {
      if(fin != null) fin.close();
   } catch(IOException e) {
      System.out.println("Error Penutupan File");
   }
}

Pada pendekatan ini, setiap error, termasuk error pembukaan file, bisa ditangani hanya oleh satu statemen each. Karena keringkasannya, pendekatan ini digunakan oleh banyak contoh I/O pada buku ini. Pendekatan ini tidak cocok pada kasus ketika Anda ingin menangani secara terpisah kegagalan pembukaan file, seperti diakibatkan ketika user salah memasukkan nama file. Pada kasus seperti itu, Anda ingin memberikan user kesempatan lagi untuk memasukkan nama file sampai benar, sebelum program memasuki blok try yang mengakses file tersebut.

Untuk menuliskan data ke sebuah file, Anda dapat menggunakan metode write() yang didefinisikan oleh FileOutputStream. Bentuk paling sederhana dari metode ini adalah:

void write(int nilaibyte) throws IOException

Metode ini menuliskan byte yang diberikan melalui nilaibyte ke dalam file. Meskipun nilaibyte dideklarasikan sebagai integer, hanya delapan bit orde rendah saja yang akan dituliskan ke dalam file. Jika error terjadi selama penulisan file, eksepsi IOException akan dilemparkan. Contoh berikut menggunakan write() untuk menyalin sebuah file:

/* Menyalin sebuah file.
 * Untuk menggunakan program ini, berikan nama
 * dari file sumber dan file tujuan.
 * Sebagai contoh, untuk menyalin sebuah file 
 * dengan nama PERTAMA.TXT ke sebuah file dengan
 * nama KEDUA.TXT, gunakan perintah baris berikut:
 * java SalinFile PERTAMA.TXT KEDUA.TXT
*/
import java.io.*;

public class SalinFile {
   public static void main(String args[]) throws IOException
   {
      int i;
      FileInputStream fin = null;
      FileOutputStream fout = null;
 
      // Pertama, memastikan bahwa kedua file telah diberikan.
      if(args.length != 2) {
         System.out.println("Kegunaan: SalinFile dari ke");
         return;
      }
 
      // Menyalin sebuah file.
      try {
         // Mencoba membuka kedua file
      fin = new FileInputStream(args[0]);
         fout = new FileOutputStream(args[1]);
 
         do {
            i = fin.read();
            if(i != -1) fout.write(i);
         } while(i != -1);
      } catch(IOException e) {
         System.out.println("Error I/O: " + e);
      } finally {
         try {
            if(fin != null) fin.close();
         } catch(IOException e2) {
            System.out.println("Error Penutupan File Masukan");
         }
         try {
            if(fout != null) fout.close();
         } catch(IOException e2) {
            System.out.println("Error Penutupan File Keluaran");
         }
     }
   }
}

Pada program, perhatikan bahwa dua blok try terpisah dipakai ketika melakukan penutupan file. Ini untuk memastikan bahwa kedua file telah ditutup, bahkan jika pemanggilan terhadap fin.close() melemparkan sebuah eksepsi.


Secara Otomatis Menutup File
Pada bagian sebelumnya, program-program contoh membuat pemanggilan eksplisit terhadap close() untuk menutup file setelah file itu tidak lagi dibutuhkan. Seperti disebutkan, ini merupakan cara bagaimana file ditutup ketika menggunakan versi Java sebelum JDK 7. Meskipun pendekatan ini masih valid dan berguna, JDK 7 telah menambahkan sebuah fitur baru yang menawarkan cara lain dalam mengelola sumberdaya, seperti aliran file, dengan mengotomatisasi proses penutupan file. Fitur ini, kadangkala disebut pula dengan pengelolaan sumberdaya otomatis, atau ARM (automatic resource management), didasarkan pada versi diperluas dari statemen try. Keuntungan utama dari ARM adalah mencegah situasi dimana file secara tidak sengaja tidak ditutup setelah file tersebut tidak lagi diperlukan. Seperti yang telah dijelaskan, pengabaian penutupan file dapat menyebabkan bocornya memori dan bisa menyebabkan permasalahan lain.

ARM didasarkan pada bentuk diperluas dari statemen try. Berikut adalah bentuk umumnya:

try (spesifikasi-sumberdaya) {
   // gunakan sumberdaya
}

Di sini, spesifikasi-sumberdaya adalah sebuah statemen yang mendeklarasikan dan menginisialisasi sumberdaya, seperti aliran file. Statemen ini memuat sebuah deklarasi variabel dimana di dalamnya variabel tersebut diinisialisasi dengan sebuah referensi ke objek yang sedang dikelola. Ketika blok try berakhir, sumberdaya tersebut akan secara otomatis dibebaskan. Pada kasus file, ini berarti bahwa file secara otomatis ditutup. Jadi, tidak diperlukan lagi untuk memanggil metode close(). Tentu, bentuk dari try ini dapat pula mencantumkan klausa catch dan finally. Bentuk baru try ini dinamakan pula dengan statemen try-dengan-sumberdaya.

Statemen try-dengan-sumberdaya dapat dipakai hanya dengan sumberdaya yang mengimplementasikan antarmuka AutoCloseable yang didefinisikan oleh java.lang. Antarmuka ini mendefinisikan metode close(). Antarmuka AutoCloseable diwarisi oleh antarmuka Closeable pada java.io. Kedua antarmuka ini diimplementasikan oleh kelas-kelas aliran. Jadi, try-dengan-sumberdaya dapat dipakai ketika bekerja dengan aliran, termasuk aliran file.

Sebagai contoh pertama dalam menutup file secara otomatis, berikut adalah versi terbaru dari program TampilFile yang menggunakannya:

/*Menampilkan sebuah file teks.
* Untuk menggunakan program ini,
* tetapkan nama file yang akan Anda lihat
/* Versi dari program TampilFile ini menggunakan sebuah statemen
 * try-dengan-sumberdaya untuk secara otomatis menutup file setelah
 * tidak lagi dibutuhkan. Kode ini mensyaratkan JDK 7 atau lebih baru.
*/
import java.io.*;

public class TampilFileSumberdaya {
   public static void main(String args[])
   {
      int i;
	
      // Pertama, memastikan bahwa nama file telah diberikan.
      if(args.length != 1) {
         System.out.println("Nama file belum diberikan");
         return;
      }
      
      // Kode berikut menggunakan statemen try-dengan-sumberdaya untuk
      // membuka file dan kemudian secara otomatis menutupnya
      // ketika blok try berakhir.
      try(FileInputStream fin = new FileInputStream(args[0])) {
         do {
            i = fin.read();
            if(i != -1) System.out.print((char) i);
         } while(i != -1);
      } catch(FileNotFoundException e) {
         System.out.println("File Tidak Ditemukan.");
      } catch(IOException e) {
    	  System.out.println("Error I/O Error Terjadi");
      }
   }
}

Pada program, perhatikan khusus pada bagaimana file dibuka di dalam statemen try:

Try(FileInputStream fin = new FileInputStream(args[0])) {

Perhatikan bagaimana porsi spesifikasi-sumberdaya dari try mendeklarasikan sebuah objek FileInputStream dengan nama fin, yang kemudian ditugasi sebuah referensi yang menunjuk ke file yang dibuka oleh konstruktornya. Jadi, pada versi program ini, variabel fin adalah variabel lokal di dalam blok try, yang diciptakan ketika eksekusi program memasuki try. Ketika kendali eksekusi program meninggalkan blok try tersebut, aliran yang melekat pada fin secara otomatis ditutup oleh pemanggilan implisit terhadap close(). Anda tidak perlu memanggil close() secara  eksplisit, yang berarti bahwa Anda tidak perlu repot untuk menutup file. Inilah keuntungan utama dari penggunaan try-dengan-sumberdaya.

Anda dapat mengelola lebih dari satu sumberdaya di dalam satu statemen try. Untuk melakukannya, Anda hanya perlu memisahkan setiap spesifikasi sumberdaya dengan tanda titik-koma. Program berikut menunjukkan salah satu contohnya. Program tersebut menulis-ulang program SalinFile sebelumnya sehingga ia sekarang menggunakan satu statemen try-dengan-sumberdaya untuk mengelola baik fin maupun fout.

/*Versi program ini menggunakan statemen try-dengan-sumberdaya.
 * Mendemonstrasikan dua sumberdaya (pada kasus ini dua file)
 * yang dikelola oleh satu statemen try.
 */

import java.io.*;

public class TampilFileSumberdaya2 {
   public static void main(String args[])
   {
      int i;
	
      // Pertama, memastikan bahwa nama file telah diberikan.
      if(args.length != 1) {
         System.out.println("Salin file dari dan ke");
         return;
      }
      
      // Membuka dan mengelola dua file
      try(FileInputStream fin = new FileInputStream(args[0]);
          FileOutputStream fout = new FileOutputStream(args[1])) {
         do {
            i = fin.read();
            if(i != -1) System.out.print((char) i);
         } while(i != -1);

      }catch(IOException e) {
    	  System.out.println("Error I/O Error Terjadi");
      }
   }
}

Pada program ini, perhatikan bagaimana file masukan dan file keluaran dibuka di dalam blok try:

try(FileInputStream fin = new FileInputStream(args[0]);
    FileOutputStream fout = new FileOutputStream(args[1])) 
{
   //…

Setelah blok try ini berakhir dieksekusi, kedua fin maupun fout selalu akan ditutup. Jika Anda membandingkan versi program ini dengan versi sebelumnya, Anda akan melihat kode yang lebih ringkas.


Pemodifikasi transient dan volatile
Java mendefinisikan dua jenis pemodifikasi yang menarik: transient dan volatile. Kedua pemodifikasi ini dipakai untuk menangani situasi khusus.

Ketika sebuah variabel objek dideklarasikan sebagai transient, nilainya tidak bertahan ketika objek itu disimpan. Sebagai contoh,

class T {
   transient int a; // Nilainya tidak bertahan
   int b; // Nilainya bisa disimpan
}

}

Di sini, jika sebuah objek bertipe T disimpan ke area penyimpanan (memori), maka isi dari a tidak akan disimpan, tetapi isi dari b akan disimpan.

Pemodifikasi volatile memberitahu kompilator bahwa variabel yang dimodifikasi oleh volatile dapat diubah oleh bagian lain dari program Anda. Salah satu situasi seperti umumnya melibatkan program multithread. Pada program multithread, adakalanya dua atau lebih thread memakai bersama variabel tertentu. Demi pertimbangan efisiensi, setiap thread dapat memiliki salinan sendiri dari variabel berbagi-pakai tersebut. Salinan riil (master) dari variabel diperbarui pada sejumlah waktu, seperti ketika metode synchronized dieksekusi. Meskipun pendekatan ini dapat dilakukan, adakalanya hal itu tidak efisien. Pada sejumlah kasus, apa yang penting adalah salinan master dari sebuah variabel selalu merefleksikan keadaan terkininya. Untuk memastikannya, variabel tersebut dapat dideklarasikan sebagai volatile, yang memberitahu kompilator bahwa program harus selalu menggunakan salinan master dari variabel volatile (atau setidaknya, selalu memperbarui tiap salinan agar sesuai nilainya dengan salinan master).


Menggunakan instanceof
Adakalanya, Anda perlu mengetahui tipe data dari sebuah objek pada saat program dijalankan. Misalnya, Anda memiliki satu thread eksekusi yang menghasilkan sejumlah tipe data dari objek-objek, dan thread lain yang memproses objek-objek ini. Pada situasi ini, akan bermanfaat bagi thread pemroses objek untuk mengetahui tipe data dari tiap objek ketika ia menerimanya. Pada situasi lain, pengetahuan akan tipe data objek pada saat program dijalankan itu biasanya melibatkan konversi tipe data eksplisit (casting). Dalam Java, konversi tipe data bisa menyebabkan error run-time. Banyak konversi tipe data yang tidak valid terjadi pada saat kompilasi. Tetapi, konversi tipe data yang tidak valid pada saat melibatkan hierarki pewarisan hanya bisa dideteksi saat program dijalankan. Sebagai contoh, sebuah superkelas dengan nama A dapat menghasilkan dua subkelas, B dan C. Jadi, konversi tipe data eksplisit (casting) terhadap sebuah objek B menjadi bertipe A atau konversi tipe data eksplisit (casting) terhadap sebuah objek C menjadi bertipe A merupakan konversi tipe data yang valid dalam pemrograman Java. Tetapi, konversi tipe data eksplisit (casting) terhadap sebuah objek B menjadi bertipe C (atau sebaliknya) merupakan konversi illegal atau dilarang dalam Java. Karena sebuah objek bertipe A dapat mereferensi ke objek-objek bertipe B atau C, lalu bagaimana Anda bisa mengetahui, pada saat program dijalankan, apa tipe data objek yang sebenarnya sedang direferensi sebelum mencoba mengkonversinya ke tipe data C? Bisa jadi itu adalah sebuah objek bertipe data A, B, atau C. Jika ia adalah sebuah objek bertipe B, maka eksepsi run-time akan dilemparkan. Java menyediakan operator run-time, yaitu instanceof, untuk menjawab pertanyaan ini.

Operator instanceof memiliki bentuk umum berikut:

refobjek instanceof tipedata

Di sini, refobjek adalah sebuah referensi yang menunjuk ke objek dari sebuah kelas, dan tipedata adalah sebuah tipe data kelas. Jika refobjek adalah tipedata yang dimaksud atau sebuah tipe data yang bisa dikonversi menjadi tipedata yang dimaksud, maka operator instanceof akan bernilai true. Jadi, instanceof adalah cara bagaimana program Anda mendapatkan informasi tipe data tentang sebuah objek ketika program dijalankan.

Program berikut mendemonstrasikan operator instanceof:

// Mendemonstrasikan operator instanceof.
class A {
   int i, j;
}

class B {
   int i, j;
}

class C extends A {
   int k;
}

class D extends A {
   int k;
}

public class Instanceof {
   public static void main(String args[]) {
      A a = new A();
      B b = new B();
      C c = new C();
      D d = new D();
		
      if(a instanceof A)
         System.out.println("a adalah sebuah objek bertipe data A");
		
      if(b instanceof B)
         System.out.println("b adalah sebuah objek bertipe data B");
		
      if(c instanceof C)
         System.out.println("c adalah sebuah objek bertipe data C");
		
      if(c instanceof A)
         System.out.println("c dapat dikonversi ekslisit menjadi tipe data A");
		
      if(a instanceof C)
         System.out.println("a dapat dikonversi ekslisit menjadi tipe data C");
		
      System.out.println();
		
      // Membandingkan tipe data dari tipe-tipe terderivasi
      A ob;
      ob = d; // A mereferensi ke d
      System.out.println("ob sekarang mereferensi ke d");
      
      if(ob instanceof D)
         System.out.println("ob adalah sebuah objek bertipe data D");
		
      System.out.println();
		
      ob = c; // A mereferensi ke c
      System.out.println("ob sekarang mereferensi ke c");
		
      if(ob instanceof D)
         System.out.println("ob dapat dikonversi ekslisit menjadi tipe data D");
      else
         System.out.println("ob tidak dapat dikonversi ekslisit menjadi tipe data D");
		
      if(ob instanceof A)
         System.out.println("ob dapat dikonversi ekslisit menjadi tipe data A");
      System.out.println();
		
      // semua objek dapat dikonversi menjadi Object
      if(a instanceof Object)
         System.out.println("a dapat dikonversi ekslisit menjadi tipe data Object");
      
      if(b instanceof Object)
         System.out.println("b dapat dikonversi ekslisit menjadi tipe data Object");
      
      if(c instanceof Object)
         System.out.println("c dapat dikonversi ekslisit menjadi tipe data Object");
      
      if(d instanceof Object)
         System.out.println("d dapat dikonversi ekslisit menjadi tipe data Object");
      }
}

Program di atas menghasilkan keluaran berikut:

a adalah sebuah objek bertipe data A
b adalah sebuah objek bertipe data B
c adalah sebuah objek bertipe data C
c dapat dikonversi ekslisit menjadi tipe data A

ob sekarang mereferensi ke d
ob adalah sebuah objek bertipe data D

ob sekarang mereferensi ke c
ob tidak dapat dikonversi ekslisit menjadi tipe data D
ob dapat dikonversi ekslisit menjadi tipe data A

a dapat dikonversi ekslisit menjadi tipe data Object
b dapat dikonversi ekslisit menjadi tipe data Object
c dapat dikonversi ekslisit menjadi tipe data Object
d dapat dikonversi ekslisit menjadi tipe data Object

Operator instanceof tidak diperlukan bagi banyak program, karena, umumnya, Anda telah mengetahui tipe data objek yang sedang Anda kerjakan. Namun, adakalanya Anda bekerja menggunakan objek-objek dengan hierarki pewarisan kelas yang kompleks. Saat itulah Anda memerlukan operator ini.






No comments: