Wednesday, March 21, 2018

14. Pemrograman Generik Bagian Tiga



Wildcard Terkekang
Argumen wildcard dapat dikekang dengan cara sama seperti parameter tipe dikekang. Wildcard terkekang khususnya penting ketika Anda menciptakan sebuah tipe data generik yang akan diterapkan pada hierarki pewarisan kelas. Untuk memahaminya mengapa, Anda akan melihat contoh berikut. Perhatikan hierarki pewarisan kelas yang mengenkapsulasi koordinat:

// Koordinat dua-dimensi.
class DuaD {
   int x, y;

   DuaD(int a, int b) {
      x = a;
      y = b;
   }
}

// Koordinat tiga-dimensi.
class TigaD extends DuaD {
   int z;

   TigaD(int a, int b, int c) {
      super(a, b);
      z = c;
   }
}

// Koordinat empat-dimensi.
class EmpatD extends TigaD {
   int t;

   EmpatD(int a, int b, int c, int d) {
      super(a, b, c);
      t = d;
   }
}

Di atas hierarki pewarisan adalah kelas DuaD, yang mengenkapsulasi sebuah koordinat XY, dua-dimensi. Kelas DuaD diwarisi oleh TigaD, yang menambahkan sebuah dimensi ketiga, yang menciptakan koordinat XYZ. TigaD diwarisi oleh EmpatD, yang menambahkan sebuah dimensi keempat, yang menghasilkan koordinat empat-dimensi.

Yang ditampilkan selanjutnya adalah sebuah kelas generik dengan nama Koord, yang menyimpan sebuah array yang memuat koordinat-koordinat:

//Kelas ini memuat sebuah array yang memuat objek-objek koordinat.
class Koord<T extends DuaD> {
   T[] arrayKoordinat;

   Koord(T[] o) { arrayKoordinat = o; }
}

Perhatikan bahwa Koord menetapkan sebuah parameter tipe yang dikekang oleh DuaD. Ini berarti bahwa sembarang array yang disimpan di dalam objek Koord akan memuat objek-objek bertipe DuaD atau sub-subkelasnya.

Sekarang, asumsikan bahwa Anda ingin menuliskan sebuah metode yang menampilkan koordinat X dan Y untuk setipa elemen pada array arrayKoordinat dari sebuah objek Koord. Karena semua tipe data dari objek-objek Koord memiliki sedikitnya dua koordinat (X dan Y), maka ini dilakukan menggunakan wildcard, seperti ditunjukkan di sini:

static void tampilXY(Koord<?> c) {
   System.out.println("Koordinat X Y:");

   for(int i=0; i < c.arrayKoordinat.length; i++)
      System.out.println(c.arrayKoordinat[i].x + " " + c.arrayKoordinat[i].y);

   System.out.println();
}

Karena Koord merupakan sebuah tipe data generik terkekang yang menetapkan DuaD sebagai kekangan atas, semua objek yang dapat dipakai untuk menciptakan objek Koord akan berupa array dengan tipe DuaD, atau dengan tipe kelas-kelas yang diderivasi dari DuaD. Jadi, tampilXY() dapat menampilkan isi dari sembarang objek Koord.

Namun, bagaimana jika Anda ingin menciptakan sebuah metode yang menampilkan koordinat X, Y, dan Z dari objek TigaD atau EmpatD? Permasalahannya adalah bahwa tidak semua objek Koord akan memiliki tiga koordinat, karena objek Koord<DuaD> hanya akan memiliki X dan Y. Oleh karena itu, bagaimana Anda menuliskan sebuah metode yang menampilkan koordinat X, Y, dan Z untuk objek Koord<TigaD> dan Koord<EmpatD>, sembari mencegah metode itu untuk digunakan dengan objek Koord<DuaD>? Jawabannya adalah argumen wildcard terkekang.

Wildcard terkekang menetapkan kekangan atas atau kekangan bawah untuk argumen tipe. Ini memampukan Anda untuk membatasi tipe-tipe data dari objek yang dapat diproses oleh metode. Wildcard terkekang paling umum digunakan adalah kekangan atas, yang diciptakan menggunakan klausa extends.

Dengan menggunakan wildcard terkekang, Anda dengan mudah menciptakan sebuah metode yang menampilkan koordinat X, Y, dan Z dari sebuah objek Koord, jika objek itu sebenarnya memiliki ketiga koordinat tersebut. Misalnya, metode tampilXYZ() menampilkan koordinat X, Y, dan Z dari elemen-elemen yang disimpan di dalam sebuah objek Koord, jika elemen-elemen tersebut sebenarnya bertipe data aktual TigaD (atau yang diderivasi dari TigaD):

static void tampilXYZ(Koord<? extends TigaD> c) {
   System.out.println("Koordinat X Y Z:");
      
   for(int i=0; i < c.arrayKoordinat.length; i++)
      System.out.println(c.arrayKoordinat[i].x + " " +
         c.arrayKoordinat[i].y + " " +
         c.arrayKoordinat[i].z);

   System.out.println();
}

Perhatikan bahwa klausa extends telah ditambahkan pada wildcard pada deklarasi dari parameter c. Itu menyatakan bahwa ? dapat cocok dengan setiap tipe data sepanjang tipe data tersebut adalah TigaD atau kelas yang diderivasi dari TigaD. Jadi, klausa extends menetapkan sebuah kekangan atas. Karena kekangan ini, metode tampilXYZ() dapat dipanggil dengan referensi yang menunjuk ke objek bertipe Koord<TigaD> atau Koord<EmpatD>, tetapi bukan dengan referensi yang menunjuk ke Koord<DuaD>. Jika Anda melakukan kesalahan ini, error kompilasi akan diberikan oleh kompilator Java untuk memastikan keamanan tipe data.

Berikut adalah keseluruhan program yang mendemonstrasikan penggunaan argumen wildcard terkekang:

// Koordinat dua-dimensi.
class DuaD {
   int x, y;

   DuaD(int a, int b) {
      x = a;
      y = b;
   }
}

// Koordinat tiga-dimensi.
class TigaD extends DuaD {
   int z;

   TigaD(int a, int b, int c) {
      super(a, b);
      z = c;
   }
}

// Koordinat empat-dimensi.
class EmpatD extends TigaD {
   int t;

   EmpatD(int a, int b, int c, int d) {
      super(a, b, c);
      t = d;
   }
}

//Kelas ini memuat sebuah array yang memuat objek-objek koordinat.
class Koord<T extends DuaD> {
   T[] arrayKoordinat;

   Koord(T[] o) { arrayKoordinat = o; }
}

//Mendemonstrasikan sebuah wildcard terkekang.
class WildcardTerkekang {
   static void tampilXY(Koord<?> c) {
      System.out.println("Koordinat X Y:");

      for(int i=0; i < c.arrayKoordinat.length; i++)
         System.out.println(c.arrayKoordinat[i].x + " " + c.arrayKoordinat[i].y);

      System.out.println();
   }

   static void tampilXYZ(Koord<? extends TigaD> c) {
      System.out.println("Koordinat X Y Z:");
      
      for(int i=0; i < c.arrayKoordinat.length; i++)
         System.out.println(c.arrayKoordinat[i].x + " " +
            c.arrayKoordinat[i].y + " " +
            c.arrayKoordinat[i].z);

      System.out.println();
   }

   static void tampilSemua(Koord<? extends EmpatD> c) {
      System.out.println("Koordinat X Y Z T:");

      for(int i=0; i < c.arrayKoordinat.length; i++)
         System.out.println(c.arrayKoordinat[i].x + " " +
            c.arrayKoordinat[i].y + " " +
            c.arrayKoordinat[i].z + " " +
            c.arrayKoordinat[i].t);

      System.out.println();
   }
}

public class DemoWildcardTerkekang extends WildcardTerkekang {
   public static void main(String args[]) {
      DuaD dd[] = {
         new DuaD(0, 0),
         new DuaD(7, 9),
         new DuaD(18, 4),
         new DuaD(-1, -23)
      };
  
      Koord<DuaD> ddlok = new Koord<DuaD>(dd);
      System.out.println("Isi dari tdlok: ");
      tampilXY(ddlok); // OK, merupakan objek DuaD
  
      // tampilXYZ(ddlok); // Error, bukan objek TigaD
      // tampilSemua(ddlok); // Error, bukan objekEmpatD
 
      // Sekarang menciptakan objek-objek EmpatD.
      EmpatD ed[] = {
         new EmpatD(1, 2, 3, 4),
         new EmpatD(6, 8, 14, 8),
         new EmpatD(22, 9, 4, 9),
         new EmpatD(3, -2, -23, 17)
      };
  
      Koord<EmpatD> edlok = new Koord<EmpatD>(ed);
      System.out.println("Isi dari edlok: ");
      // Semua ini OK.
      tampilXY(edlok);
      tampilXYZ(edlok);
      tampilSemua(edlok);
   }
}

Jika dijalankan, program ini akan menghasilkan keluaran berikut:


Isi dari tdlok: 
Koordinat X Y:
0 0
7 9
18 4
-1 -23

Isi dari edlok: 
Koordinat X Y:
1 2
6 8
22 9
3 -2

Koordinat X Y Z:
1 2 3
6 8 14
22 9 4
3 -2 -23

Koordinat X Y Z T:
1 2 3 4
6 8 14 8
22 9 4 9
3 -2 -23 17

Perhatikan dua baris kode yang dijadikan komentar berikut:

// tampilXYZ(ddlok); // Error, bukan objek TigaD
// tampilSemua(ddlok); // Error, bukan objekEmpatD

Karena ddlok merupakan sebuah objek Koord<DuaD>, ia tidak bisa dipakai untuk memanggil tampilXYZ() atau tampilSemua() karena argumen wildcard terkekang pada deklarasinya mencegah hal itu dilakukan. Untuk membuktikannya, Anda bisa menghapus simbol komentar tersebut, dan kemudian mencoba mengkompilasi ulang program. Anda akan menerima error kompilasi karena ketidakcocokan tipe data.

Secara umum, untuk menetapkan kekangan atas untuk sebuah wildcard, Anda menggunakan jenis ekspresi wildcard berikut:

<? extends superkelas>

dimana superkelas adalah nama kelas yang berperan sebagai kekangan atas. Ingat, ini merupakan klausa inklusif karena kelas pembentuk kekangan atas (yaitu superkelas) juga berada di dalam kekangan.

Anda dapat pula menetapkan kekangan bawah untuk sebuah wildcard dengan menambahkan klausa super pada deklarasi wildcard: Berikut adalah bentuk umumnya:

<? super subkelas>

Pada kasus ini, hanya kelas-kelas yang menjadi superkelas dari subkelas yang bisa dijadikan argumen. Ini juga merupakan klausa inklusif.


Menciptakan Metode Generik
Seperti ditunjukkan pada contoh-contoh sebelumnya, metode yang ada di dalam sebuah kelas generik dapat memanfaatkan parameter tipe. Metode tersebut secara otomatis adalah metode generik. Tetapi, Anda dimungkinkan untuk mendeklarasikan metode generik yang menggunakan satu atau lebih parameter tipenya sendiri. Jadi, Anda bisa menciptakan sebuah metode generik yang diapit atau dibungkus di dalam kelas tak-generik.

Anda akan memulainya dengan melihat contoh. Program berikut mendeklarasikan sebuah kelas tak-generik dengan nama DemoMetodeGenerik dan sebuah metode generik static dengan nama apaDiDalam() di dalam kelas tersebut. Metode tersebut menentukan apakah sebuah objek merupakan anggota dari suatu array. Metode ini dapat dipakai oleh sembarang tipe objek dan array sepanjang array tersebut memuat objek-objek yang kompatibel dengan tipe objek yang dicari.

// Mendemonstrasikan sebuah metode generik sederhana.

public class DemoMetodeGenerik {
   // Menentukan apakah sebuah objek ada di dalam array atau tidak.
   static <T extends Comparable<T>, V extends T> boolean apaDiDalam(T x, V[] y) {
      for(int i=0; i < y.length; i++)
         if(x.equals(y[i])) return true;
 
      return false;
   }
 
   public static void main(String args[]) {
      // Menggunakan apaDiDalam() pada nilai-nilai Integer.
      Integer arrayNilai[] = { 1, 2, 3, 4, 5 };
 
      if(apaDiDalam(2, arrayNilai))
         System.out.println("2 ada di dalam arrayNilai");
 
      if(!apaDiDalam(7, arrayNilai))
         System.out.println("7 tidak ada di dalam arrayNilai");
 
      System.out.println();
 
      // Menggunakan apaDiDalam() pada nilai-nilai String.
      String arrayString[] = { "satu", "dua", "tiga", "empat", "lima" };
 
      if(apaDiDalam("dua", arrayString))
         System.out.println("dua ada di dalam arrayString");
 
      if(!apaDiDalam("tujuh", arrayString))
         System.out.println("tujuh tidak ada di dalam arrayString");
 
      // Ini tidak akan bisa dikompilasi karena tipe data tidak kompatibel.
      // if(apaDiDalam("dua", arrayNilai))
      // System.out.println("dua ada di dalam arrayString");
 }
}

Jika dikompilasi, program di atas akan menghasilkan keluaran berikut:

2 ada di dalam arrayNilai
7 tidak ada di dalam arrayNilai

dua ada di dalam arrayString
tujuh tidak ada di dalam arrayString

Periksa metode apaDiDalam() lebih dekat. Pertama, perhatikan bagaimana ia dideklarasikan oleh baris ini:

static <T extends Comparable<T>, V extends T> boolean apaDiDalam(T x, V[] y) {

static <T extends Comparable<T>, V extends T> boolean apaDiDalam(T x, V[] y) {

Parameter-parameter tipe dideklarasikan sebelum tipe data nilai balik dari metode. Perhatikan pula bahwa T mewarisi Comparable<T>. Comparable merupakan sebuah antarmuka yang dideklarasikan di dalam java.lang. Sebuah kelas yang mengimplementasikan Comparable perlu mendefinisikan objek-objek yang dapat diurutkan. Jadi, dengan melakukan pengekangan atas dari Comparable, hal itu untuk memastikan bahwa metode apaDiDalam() hanya dapat dipakai pada objek-objek yang dapat dibandingkan. Comparable adalah antarmuka generik, dan parameter tipenya menetapkan tipe data objek yang dibandingkan. Selanjutnya, perhatikan bahwa tipe V dikekang-atas oleh T. Jadi, V harus bertipe sama dengan tipe T, atau subkelas dari T. Relasi ini menegaskan bahwa apaDiDalam() dapat dipanggil hanya dengan argumen-argumen yang kompatibel satu sama lain. Selain itu, perhatikan bahwa apaDiDalam() dideklarasikan static, yang membuatnya independe dari setiap objek.


Selanjutnya  >>>



No comments: