Wednesday, March 21, 2018

14. Pemrograman Generik Bagian Lima



Menggunakan Superkelas Generik
Berikut adalah sebuah contoh sederhana dari sebuah hierarki pewarisan yang menggunakan superkelas generik:

// Sebuah contoh hierarki pewarisan kelas generik.
class Gen<T> {
   T ob;

   Gen(T o) {
      ob = o;
   }

   // Menghasilkan ob.
   T getob() {
      return ob;
   }
}

// Sebuah subkelas dari Gen.
class Gen2<T> extends Gen<T> {
   Gen2(T o) {
      super(o);
   }
}

Pada hierarki ini, Gen2 mewarisi kelas generik Gen. Perhatikan bagaimana Gen2 dideklarasikan oleh baris berikut:

class Gen2<T> extends Gen<T> {

Parameter tipe T ditetapkan oleh Gen2 dan juga dilewatkan kepada Gen pada klausa extends. Ini berarti bahwa apapun tipe data yang dilewatkan kepada Gen2 harus pula dilewatkan kepada Gen. Sebagai contoh, deklarasi ini:

Gen2<Integer> nil = Gen2<Integer>(100);

melewatkan Integer sebagai parameter tipe kepada Gen. Jadi, ob di dalam porsi Gen dari Gen2 akan bertipe data Integer.

Perhatikan pula bahwa Gen2 tidak menggunakan parameter tipe T kecuali untuk mendukung superkelas Gen. Jadi, meskipun jika sebuah subkelas dari superkelas generik bukanlah kelas generik, subkelas tersebut harus menetapkan parameter tipe yang diperlukan oleh superkelas generiknya.

Tentu, subkelas bebas dalam menambahkan parameter tipenya sendiri, jika diperlukan. Sebagai contoh, berikut adalah sebuah variasi pada hierarki sebelumnya dimana Gen2 menambahkan sebuah parameter tipenya sendiri:

// Sebuah subkelas dapat menambahkan parameter tipenya sendiri.
class Gen<T> {
   T ob; // mendeklarasikan sebuah objek bertipe T

   // Melewatkan kepada konstruktor sebuah referensi ke
   // sebuah objek bertipe T.
   Gen(T o) {
      ob = o;
   }

   // Menghasilkan ob.
   T getob() {
      return ob;
   }
}

// Sebuah subkelas dari Gen yang mendefinisikan parameter
// tipe kedua, dinamakan V.
class Gen2<T, V> extends Gen<T> {
   V ob2;

   Gen2(T o, V o2) {
      super(o);
      ob2 = o2;
   }

   V getob2() {
      return ob2;
   }
}

//Menciptakan sebuah objek bertipe Gen2
public class DemoHierarki {
   public static void main(String args[]) {
      // Menciptakan sebuah objek Gen2 untuk String dan Integer.
      Gen2<String, Integer> x =
         new Gen2<String, Integer>("Nilai = ", 99);
  
      System.out.print(x.getob());
      System.out.println(x.getob2());
   }
}

Perhatikan deklarasi dari versi Gen2 ini, yang ditunjukkan di sini:

class Gen2<T, V> extends Gen<T> {

Di sini, T adalah tipe data yang dilewatkan kepada Gen, dan V adalah tipe data yang spesifik pada Gen2. Parameter V dipakai untuk mendeklarasikan sebuah objek dengan nama ob2, dan sebagai tipe nilai balik untuk metode getob2(). Pada main(), sebuah objek Gen2 diciptakan dimana di dalamnya parameter tipe T adalah sebuah String, dan parameter tipe V adalah Integer.

Program menampilkan hasil berikut:

Nilai = 99


Subkelas Generik
Sebuah kelas tak-generik bisa dijadikan superkelas bagi subkelas generik. Misalnya, perhatikan program ini:

// Sebuah kelas tak-generik dapat menjadi superkelas
// dari sebuah subkelas generik.

// Sebuah kelas tak-generik.
class TakGen {
   int nil;

   TakGen(int i) {
    nil = i;
   }

   int getnum() {
      return nil;
   }
}

// Sebuah subkelas generik.
class Gen<T> extends TakGen {
   T ob; // mendeklarasikan sebuah objek bertipe T

   // Melewatkan kepada konstruktor sebuah referensi ke
   // sebuah objek bertipe T.
   Gen(T o, int i) {
      super(i);
      ob = o;
   }

   // Menghasilkan ob.
   T getob() {
      return ob;
   }
}

//Menciptakan sebuah objek Gen.
public class DemoHierarki2 {
   public static void main(String args[]) {
      // Menciptakan sebuah objek Gen untuk String.
      Gen<String> w = new Gen<String>("Hallo", 47);
  
      System.out.print(w.getob() + " ");
      System.out.println(w.getnum());
   }
}

Ketika dijalankan, program ini menghasilkan keluaran berikut:

Hallo 47

Pada program, perhatikan bagaimana Gen mewarisi TakGen pada deklarasi berikut:

class Gen<T> extends TakGen {

Karena TakGen bukan kelas generik, tidak ada argumen tipe yang diberikan. Jadi, meskipun Gen mendeklarasikan parameter tipe T, hal itu tidak diperlukan oleh (atau tidak dapat digunakan oleh) TakGen. Jadi, TakGen diwarisi oleh Gen dengan cara normal.


Perbandingan Tipe Run-Time Di Dalam Hierarki Pewarisan Generik
Operator instanceof menghasilkan true jika sebuah objek adalah tipe data yang ditetapkan atau dapat dicast menjadi tipe yang ditetapkan. Operator instanceof dapat diterapkan terhadap objek dari kelas generik. Kelas berikut mendemonstrasikan beberapa implikasi kompatibilitas tipe dari sebuah hierarki pewarisan generik.

// Menggunakan operator instanceof pada hierarki pewarisan generik.
class Gen<T> {
   T ob;

   Gen(T o) {
      ob = o;
   }

   // Menghasilkan ob.
   T getob() {
      return ob;
   }
}

// Subkelas dari Gen.
class Gen2<T> extends Gen<T> {
   Gen2(T o) {
      super(o);
   }
}

// Mendemonstrasikan implikasi kompatibilitas
// tipe data run-timedari hierarki pewarisan generik.
public class DemoHierarki3 {
   public static void main(String args[]) {
      // Menciptakan sebuah objek Gen untuk Integer.
      Gen<Integer> iOb = new Gen<Integer>(88);
  
      // Menciptakan sebuah objek Gen2 untuk Integer.
      Gen2<Integer> iOb2 = new Gen2<Integer>(99);
  
      // Menciptakan sebuah objek Gen2 untuk String.
      Gen2<String> strOb2 = new Gen2<String>("Uji Generik");
  
      // Melihat apakah iOb2 adalah objek tertentu dari Gen2.
      if(iOb2 instanceof Gen2<?>)
         System.out.println("iOb2 adalah objek dari Gen2");
  
      // Melihat apakah iOb2 adalah objek tertentu dari Gen.
      if(iOb2 instanceof Gen<?>)
         System.out.println("iOb2 adalah objek dari Gen");
  
      System.out.println();
  
      // Melihat apakah strOb2 adalah objek tertentu dari Gen2.
      if(strOb2 instanceof Gen2<?>)
         System.out.println("strOb2 adalah objek dari Gen2");
  
      // Melihat apakah strOb2 adalah objek tertentu dari Gen.
      if(strOb2 instanceof Gen<?>)
         System.out.println("strOb2 adalah objek dari Gen");
  
      System.out.println();
  
      // Melihat apakah iOb iadalah objek dari Gen2, tetapi bukan.
      if(iOb instanceof Gen2<?>)
         System.out.println("iOb adalah objek dari Gen2");
  
      // Melihat apakah iOb adalah objek dari Gen, tetapi bukan.
      if(iOb instanceof Gen<?>)
         System.out.println("iOb adalah objek dari Gen");
  
      // Kode berikut tidak dapat dikompilasi karena
      // info tipe generik tidak ada saat run time.
      // if(iOb2 instanceof Gen2<Integer>)
         // System.out.println("iOb2 adalah objek dari Gen2<Integer>");
      }
}

Ketika dijalankan, program tersebut menghasilkan keluaran:

iOb2 adalah objek dari Gen2
iOb2 adalah objek dari Gen

strOb2 adalah objek dari Gen2
strOb2 adalah objek dari Gen

iOb adalah objek dari Gen

Pada program ini, Gen2 merupakan subkelas dari Gen, yang merupakan kelas generik dengan parameter tipe T. Pada main(), ada tiga objek yang diciptakan. Yang pertama adalah iOb, yang merupakan sebuah objek bertipe Gen<Integer>. Yang kedua adalah iOb2, yang merupakan sebuah objek dari Gen2<Integer>. Terakhir, strOb2 merupakan sebuah objek bertipe Gen2<String>.

Kemudian, program melakukan uji instanceof pada tipe data dari iOb2:

// Melihat apakah iOb2 adalah objek tertentu dari Gen2.
if(iOb2 instanceof Gen2<?>)
   System.out.println("iOb2 adalah objek dari Gen2");
  
// Melihat apakah iOb2 adalah objek tertentu dari Gen.
if(iOb2 instanceof Gen<?>)
   System.out.println("iOb2 adalah objek dari Gen");

Seperti yang ditunjukkan pada keluaran program, keduanya berhasil. Pada uji pertama, iOb2 diperiksa terhadap Gen2<?>. Uji ini berhasil karena ia hanya memastikan bahwa iOb2 adalah sebuah objek dengan tipe tertentu dari objek Gen2. Penggunaan wildcard memampukan instanceof untuk menetukan apakah iOb2 adalah sebuah objek dengan tipe tertentu dari Gen2. Selanjutnya, iOb2 diuji terhadap Gen<?>, tipe data superkelas. Ini juga bernilai true karena iOb2 adalah bentuk tertentu dari Gen, superkelas. Beberapa baris berikutnya pada main() menunjukkan runtun sama (dan hasil sama) untuk strOb2.

Selanjutnya, iOb, merupakan sebuah objek dari Gen<Integer> (superkelas), diuji dengan baris-baris kode berikut:

// Melihat apakah iOb iadalah objek dari Gen2, tetapi bukan.
if(iOb instanceof Gen2<?>)
   System.out.println("iOb adalah objek dari Gen2");
  
// Melihat apakah iOb adalah objek dari Gen, tetapi bukan.
if(iOb instanceof Gen<?>)
   System.out.println("iOb adalah objek dari Gen");

Statemen if pertama gagal karena iOb bukan tipe tertentu dari objek Gen2. Statemen if kedua berhasil karena iOb merupakan tipe tertentu dari objek Gen.

Sekarang, lihat lebih dekat baris-baris kode yang dijadikan komentar berikut:

// Kode berikut tidak dapat dikompilasi karena
// info tipe generik tidak ada saat run time.
// if(iOb2 instanceof Gen2<Integer>)
   // System.out.println("iOb2 adalah objek dari Gen2<Integer>");

Seperti dinyatakan pada komentar, baris-baris kode ini tidak bisa dikompilasi karena mencoba membandingkan iOb2 dengan tipe data spesifik dari Gen2, pada kasus ini, Gen2<Integer>. Ingat, tidak ada informasi tipe data generik pada saat run time. Oleh karena itu, tidak ada cara bagi instanceof untuk mengetahui apakah iOb2 merupakan objek Gen2<Integer> atau tidak.


Mendefinisikan-Ulang (Overriding) Pada Kelas Generik
Sebuah metode pada kelas generik dapat didefinisikan-ulang sama seperti metode biasa. Sebagai contoh, perhatikan program berikut dimana di dalamnya metode getob() didefinisikan-ulang:

// Mendefinisikan-ulang metode generik di dalam kelas generik.
class Gen<T> {
   T ob; // Mendeklarasikan sebuah objek bertipe T

   // Melewatkan kepada konstruktor sebuah referensi ke
   // sebuah objek bertipe T.
   Gen(T o) {
      ob = o;
   }

   // Menghasilkan ob.
   T getob() {
      System.out.print("Metode getob() dari kelas Gen: " );
      return ob;
   }
}

// Sebuah subkelas dari Gen yang mendefinisikan-ulang metode getob().
class Gen2<T> extends Gen<T> {
   Gen2(T o) {
      super(o);
   }

   // Mendefinisikan-ulang (overriding) metode getob().
   T getob() {
      System.out.print("Metode getob() dari subkelas Gen2: ");
      return ob;
   }
}

//Mendemonstrasikan pendefinisian-ulang metode generik.
public class DemoOverride {
   public static void main(String args[]) {
      // Menciptakan sebuah objek Gen untuk Integer.
      Gen<Integer> iOb = new Gen<Integer>(88);
  
      // Menciptakan sebuah objek Gen2 untuk Integer.
      Gen2<Integer> iOb2 = new Gen2<Integer>(99);
  
      // Menciptakan sebuah objek Gen2 untuk Integer.
      Gen2<String> strOb2 = new Gen2<String> ("Uji Generik");
  
      System.out.println(iOb.getob());
      System.out.println(iOb2.getob());
      System.out.println(strOb2.getob());
   }
}

Program ini menghasilkan keluaran berikut:

Metode getob() dari kelas Gen: 88
Metode getob() dari subkelas Gen2: 99
Metode getob() dari subkelas Gen2: Uji Generik

Seperti ditegaskan oleh keluaran program, versi terdefinisi-ulang dari getob() dipanggil untuk objek bertipe Gen2, tetapi versi superkelas dipanggil untuk objek bertipe Gen.


Inferensi Tipe Data Dengan Generik
Dimulai sejak JDK 7, Anda diperbolehkan untuk memperpendek sintaksis yang dipakai untuk menciptakan sebuah objek dari suatu tipe generik. Untuk memulainya, perhatikan kelas generik berikut:

class KelasKu<T, V> {
   T ob1;
   V ob2;

   KelasKu(T o1, V o2) {
      ob1 = o1;
      ob2 = o2;
   }

   // ...
}

Sebelum JDK 7, untuk menciptakan sebuah objek dari KelasKu, Anda perlu menggunakan sebuah statemen seperti ini:

KelasKu<Integer, String> kkOb = 
   new KelasKu<Integer, String>(98, Sebuah String);

Di sini, argumen-argumen tipe (yaitu Integer dan String) ditetapkan dua kali: pertama, ketika kkOb dideklarasikan, dan kedua, ketika sebuah objek KelasKu diciptakan lewat new. Karena pemrograman generik dikenalkan sejak JDK5, ini merupakan bentuk yang diperlukan semua versi Java sebelum JDK 7. Meskipun tidak ada salahnya, dengan bentuk ini, hal itu cukup mubajir. Pad klausa new, tipe data dari argumen tipe dapat disimpulkan dari tipe data dari kkOb; oleh karena itu, tidak ada alasan untuk ditetapkan kedua kalinya. Untuk mengatasi masalah ini, JDK 7 menambahkan sebuah elemen sintaksis agar Anda tidak melakukannya.

Sekarang, deklarasi tersebut dapat ditulis kembali seperti berikut:

KelasKu<Integer, String> kkOb = new KelasKu<>(98, Sebuah String);

Perhatikan bahwa bagian penciptaan objek hanya menggunakan <>, yang merupakan daftar argumen tipe data kosong. Simbol <> dinamakan dengan operator diamond, yang memberitahu kompilator untuk menyimpulkan tiap argumen tipe data yang diperlukan oleh konstruktor pada ekspresi new. Keuntungan utama dari sintaksis ini adalah memperpendek statemen deklarasi yang panjang.

Sintaksis tersebut bisa digeneralisasi. Ketika inferensi (penyimpulan) tipe data dipakai, sintaksis deklarasi untuk referensi generik dan penciptaan objek memiliki bentuk umum ini:

nama-kelas<daftar-arg-tipe> nama-var =new nama-kelas<>(daftar-arg-konstruktor);

Di sini, daftar argumen dari konstruktor pada klausa new dibiarkan kosong.

Inferensi tipe data dapat pula diterapkan pada pelewatan parameter. Sebagai contoh, jika metode berikut ditambahkan pada KelasKu:

boolean apaSama(KelasKu<T, V> o) {
   if(ob1 == o.ob1 && ob2 == o.ob2) return true;
   else return false;
}

maka pemanggilan seperti ini dapat dilakukan:

if(kkOb.apaSama(new KelasKu<>(1, "uji"))) System.out.println("Sama");

Pada kasus ini, argumen tipe data untuk argumen yang dilewatkan kepada apaSama() dapat disimpulkan (diinferensi) dari tipe parameter.




No comments: