Tuesday, December 20, 2016

Bab 7. Java Struktur Data dan Pemrograman GUI



Bab.7 Pemrograman Generik

Tujuan Instruksional
·         Menjelaskan keuntungan generik.
·         Menggunakan kelas dan antarmuka generik.
·         Mendefinisikan kelas dan antarmuka generik.
·         Menjelaskan mengapa tipe generik dapat meningkatkan reliabilitas dan readibilitas.
·         Mendefinisikan dan menggunakan metode generik dan tipe generik terkekang.
·         Menggunakan tipe mentah untuk kompatibilitas mundur.
·         Menjelaskan mengapa tipe generik wildcard diperlukan.
·         Mendefinisikan dan mengimplementasikan kelas matriks generik.


7.1 Introduksi

Generik merupakan kapabilitas untuk memparameterisasi tipe. Dengan kapabilitas ini, Anda bisa mendefinisikan suatu kelas atau suatu metode dengan tipe-tipe generik dimana kompiler dapat menggantikannya dengan tipe-tipe konkrit. Sebagai contoh, Anda bisa mendefinisikan suatu kelas tumpukan generik yang menyimpan elemen-elemen bertipe generik pula. Dari kelas generik ini, Anda bisa menciptakan suatu objek tumpukan untuk memuat string dan suatu objek tumpukan untuk memuat angka. Di sini, string dan angka merupakan tipe-tipe konkrit yang menggantikan tipe generik.

Keuntungan generik yang paling penting adalah untuk memampukan error agar bisa dideteksi oleh kompiler pada waktu kompilasi, bukan pada waktu runtime. Suatu kelas dan metode generik membolehkan Anda untuk menentukan tipe-tipe objek yang diijinkan dimana kelas atau metode bisa dioperasikan. Jika Anda mencoba untuk menggunakan suatu objek yang tidak kompatibel, kompiler akan mendeteksi error.

Bab ini akan menjelaskan bagaimana mendefinisikan dan menggunakan kelas, antarmuka, dan metode generik dan mendemonstrasikan bagaimana pemrograman generik digunakan untuk memperbaiki reliabilitas dan readibilitas perangkat lunak.


7.2 Motivasi dan Keuntungan
Sejak JDK 1.5, JAVA mengijinkan Anda untuk mendefinisikan kelas, antarmuka, dan metode generik. Beberapa kelas dan antarmuka di dalam JAVA API dimodifikasi menggunakan generik. Sebagai contoh, sebelum JDK 1.5 antarmuka java.lang.Comparable didefinisikan seperti ditunjukkan pada Gambar 7.1a, tetapi sejak JDK 1.5 dimodifikasi seperti ditunjukkan pada Gambar 7.1b.


Gambar 7.1 Antarmuka java.lang.Comparable didefinisikan-ulang sejak JDK 1.5 dengan suatu tipe generik


Di sini, <T> merepresentasikan suatu tipe generik formal, yang nantinya dapat digantikan dengan suatu tipe konkrit aktual. Penggantian suatu tipe generik disebut dengan instansiasi generik. Secara konvensional, huruf besar seperti T atau E digunakan untuk menandai suatu tipe generik formal.

Untuk melihat keuntungan penggunaan generik, akan diuji kode pada Gambar 7.2. Statemen dalam Gambar 7.2a mendeklarasikan bahwa c merupakan suatu variabel referensi yang bertipe Comparable dan memanggil metode compareTo untuk membandingkan suatu objek Date dengan suatu string. Ketika kode dikompilasi akan baik-baik saja, tetapi sebenarnya kode tersebut memiliki error runtime karena suatu string tidak bisa dibandingkan dengan suatu objek Date.


Gambar 7.2 Tipe generik dapat mendeteksi error pada saat runtime


Statemen dalam Gambar 7.2b mendeklarasikan bahwa c merupakan suatu variabel referensi yang bertipe Comparable<Date> dan memanggil metode compareTo untuk membandingkan suatu objek Date dengan suatu string. Kode memiliki error kompilasi, karena argumen yang dilewatkan kepada metode compareTo harus bertipe Date. Karena error dapat dideteksi pada saat kompilasi, bukan pada saat runtime, tipe generik membuat program lebih handal.

Kelas ArrayList merupakan suatu kelas generik sejak JDK 1.5. Gambar 7.3 menampilkan diagram kelas untuk ArrayList sebelum dan sejak JDK 1.5.

Sebagai contoh, statemen berikut menciptakan suatu daftar yang memuat beberapa string:

ArrayList<String> daftar = new ArrayList <String>();

Anda bisa menambahkan beberapa string (hanya string) ke dalam daftar tersebut. Sebagai contoh,

daftar.add("Hijau");

Jika Anda mencoba untuk menambahkan suatu nonstring, maka error kompilasi akan terjadi. Sebagai contoh, statemen berikut ini menjadi ilegal, karena daftar hanya boleh memuat string:
daftar.add(new Integer(1));


Gambar 7.3 Kelas ArrayList merupakan kelas generik sejak JDK 1.5


Tipe generik harus berupa tipe referensi. Anda tidak bisa menggantikan tipe generik dengan tipe primitif seperti int, double, atau char. Sebagai contoh, statemen berikut adalah salah:

ArrayList<int> intList = new ArrayList<int>();

Untuk menciptakan suatu objek ArrayList untuk nilai-nilai int, Anda harus menggunakan

ArrayList<Integer> intList = new ArrayList<Integer>();

Anda bisa menambahkan suatu nilai int pada intList. Sebagai contoh,

intList.add(5);

JAVA secara otomatis menganggap 5 menjadi new Integer(5). Hal ini dikenal dengan autoboxing.

Casting tidak diperlukan untuk mendapatkan suatu nilai dari suatu daftar dengan tipe elemen tertentu, karena kompiler telah mengetahui tipe elemen yang dimaksud. Sebagai contoh, statemen-statemen berikut menciptakan suatu daftar yang memuat string, menambahkan string pada daftar, dan mendapatkan string dari daftar tersebut:

1 ArrayList<String> daftar = new ArrayList<String>();
2 daftar.add("Red");
3 daftar.add("White");
4 String s = daftar.get(0);// Casting tidak diperlukan

Sebelum JDK 1.5, tanpa menggunakan generik, Anda harus melakukan casting terhadap nilai balik menjadi String sebagai berikut:

String s = (daftar.get(0)); // Casting diperlukan sebelum JDK 1.5

Jika elemen bertipe wrapper, seperti Integer, Double, dan Character, maka Anda dapat secara langsung menugaskan suatu elemen kepada variabel elemen primitif. Hal ini dikenal dengan autounboxing. Sebagai contoh, perhatikan kode berikut ini:

1 ArrayList<Double> daftar = new ArrayList<Double>();
2 daftar.add(5.5); // 5.5 secara otomatis dikonversi menjadi new Double(5.5)
3 daftar.add(3.0); // 3.0 secara otomatis dikonversi menjadi new Double(3.0)
4 Double objekDouble = daftar.get(0); // Casting tidak diperlukan
5 double d = daftar.get(1); // secara otomatis dikonversi menjadi double

Pada baris 2 dan 3, 5.5 dan 3.0 secara otomatis dikonversi menjadi objek-objek Double dan ditambahkan kepada daftar. Pada baris 4, elemen pertama di dalam daftar ditugaskan kepada suatu variabel Double. Pada baris 5, elemen kedua di dalam daftar ditugaskan kepada suatu variabel double. Objek di dalam daftar.get(1) secara otomatis dikonversi menjadi nilai tipe primitif.


7.3 Mendefinisikan Kelas dan Antarmuka Generik
Kelas tumpukan yang baru, dinamai GenericStack, ditampilkan pada Gambar 7.4 dan diimplementasikan pada kode7.1.


Gambar 7.4 Kelas GenericStack mengenkapsulasi penyimpanan tumpukan dan menyediakan beberapa operasi untuk memanipulasi tumpukan


Kode7.1 GenericStack.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class GenericStack<E> {
  private java.util.ArrayList<E> daftar = new java.util.ArrayList<E>();

  public int getSize() {
    return daftar.size();
  }

  public E peek() {
    return daftar.get(getSize() - 1);
  }

  public void push(E o) {
    daftar.add(o);
  }

  public E pop() {
    E o = daftar.get(getSize() - 1);
    daftar.remove(getSize() - 1);
    return o;
  }

  public boolean isEmpty() {
    return daftar.isEmpty();
  }
}

Berikut merupakan suatu contoh untuk menciptakan tumpukan yang memuat beberapa string dan menambahkan tiga string pada tumpukan tersebut:

GenericStack<String> tumpukan1 = new GenericStack<String> ();
tumpukan1.push("Balige");
tumpukan1.push("Klaten");
tumpukan1.push("Jogja");

Berikut merupakan suatu contoh untuk menciptakan tumpukan yang memuat beberapa integer dan menambahkan tiga integer pada tumpukan tersebut:

GenericStack<Integer> tumpukan2= new GenericStack<Integer> ();
tumpukan2.push("Balige"); // Autoboxing 1 menjadi new Integer(1)
tumpukan2.push("Klaten");
tumpukan2.push("Jogja");

Daripada menggunakan tipe generik, sebenarnya Anda bisa menggunakan tipe Object, yang bisa dipakai untuk sembarang tipe objek. Akan tetapi, penggunaan tipe generik dapat memperbaiki realibilitas dan readibilitas perangkat lunak, karena beberapa error dapat dideteksi pada saat kompilasi, bukan pada saat runtime. Sebagai contoh, karena tumpukan1 dideklarasikan GenericStack<String>, maka hanya string yang bisa ditambahkan pada tumpukan tersebut. Dan akan terjadi error kompilasi bila Anda mencoba menambahkan suatu integer pada tumpukan1.


7.4 Metode Generik
Anda dapat mendefinisikan antarmuka generik (misalnya, antarmuka Comparable pada Gambar 7.1b) dan kelas generik (misalnya, kelas GenericStack pada kode7.1). Anda juga dapat mendefinisikan tipe generik untuk mendefinisikan metode generik. Sebagai contoh, kode7.2 mendefinisikan suatu metode generik tampil (baris 10-14) untuk menampilkan suatu array objek. Baris 6 melewatkan suatu array yang memuat objek-objek integer untuk memanggil metode generik tampil. Baris 7 memanggil tampil dengan suatu array string.

Kode7.2 DemoMetodeGenerik.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DemoMetodeGenerik {
  public static void main(String[] args ) {
    Integer[] integer = {1, 2, 3, 4, 5};
    String[] string = {"Klaten", "Balige", "Jogja", "Siantar"};

    DemoMetodeGenerik.<Integer>tampil(integer);
    DemoMetodeGenerik.<String>tampil(string);
  }

  public static <E> void tampil(E[] daftar) {
    for (int i = 0; i < daftar.length; i++)
      System.out.print(daftar[i] + " ");
    System.out.println();
  }
}

Keluaran

1 2 3 4 5
Klaten Balige Jogja Siantar

Untuk memanggil suatu metode generik, nama metode diprefiks dengan tipe aktual yang diapit oleh sepasang kurung siku. Sebagai contoh,

DemoMetodeGenerik.<Integer>tampil(integer);
DemoMetodeGenerik.<String>tampil(string);

Tipe generik dapat ditetapkan sebagai subtipe dari tipe lain. Tipe generik seperti itu disebut tipe generik terkekang. Sebagai contoh, kode7.3 akan merevisi metode luasSama di dalam kode UjiObjekGeometri.java, untuk menguji apakah dua objek geometri memiliki luas yang sama atau tidak. Tipe generik terkekang <E extends ObjekGeometri> (baris 7) menentukan bahwa E adalah suatu subtipe generik dari ObjekGeometri. Anda harus memanggil luasSama dengan melewatkan dua instans ObjekGeometri.

Kode7.3 DemoTipeTerkekang.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DemoTipeTerkekang {
  public static void main(String[] args ) {
    PersegiPanjang persegipanjang = new PersegiPanjang(2, 2);
    Lingkaran lingkaran = new Lingkaran(2);

    System.out.println("Luas sama? " +
      DemoTipeTerkekang.<ObjekGeometri>luasSama(persegipanjang, lingkaran));
  }

  public static <E extends ObjekGeometri> boolean luasSama(
     E objek1, E objek2) {
   return objek1.dapatLuas() == objek2.dapatLuas();
  }
}

Keluaran

Luas sama? false


7.5 Tipe Mentah dan Kompatibilitas Mundur
Anda bisa menggunakan suatu kelas generik tanpa menspesifikasi tipe konkrit seperti ini:

GenericStack tumpukan = new GenericStack(); // tipe mentah

Ini ekivalen dengan

GenericStack<Object> tumpukan = new GenericStack<Object>();

Suatu kelas generik seperti GenericStack dan ArrayList yang digunakan tanpa suatu parameter tipe disebut dengan tipe mentah. Kegunaan tipe mentah dikhususkan untuk kompatibilitas mundur dengan versi JAVA sebelumnya. Sebagai contoh, tipe generik digunakan di dalam java.lang.Comparable sejak JDK 1.5, tetapi banyak kode masih menggunakan tipe mentah Comparable, seperti ditunjukkan pada kode7.4.

Kode7.4 Max.java

1
2
3
4
5
6
7
8
9
public class Max {
  /** Mengembalikan maksimum dari dua objek */
  public static Comparable max(Comparable o1 , Comparable o2) {
    if(o1.compareTo(o2)> 0)
      return o1;
    else
      return o2;
  }
}

Comparable o1 dan Comparable o2 adalah dua deklarasi tipe mentah. Tipe mentah bersifat tidak aman. Sebagai contoh, Anda bisa memanggil metode max menggunakan:

Max.max("JAVA itu Tangguh!", 23); // 23 diautobox menjadi new Integer(23)

Hal ini mengakibatkan error runtime, karena Anda tidak bisa membandingkan objek string dengan objek integer. Kompiler JAVA akan menampilkan pesan, seperti tertampil pada Gambar 7.5.


Gambar 7.5 Peringatan ditampilkan oleh kompiler


Cara yang lebih baik untuk menulis metode max adalah menggunakan suatu tipe generik, seperti ditunjukkan pada Gambar 7.5.
Kode7.5 Max1.java

1
2
3
4
5
6
7
8
9
public class Max1 {
  /** Mengembalikan maksimum dari dua objek */
  public static <E extends Comparable<E>> E max(E o1, E o2) {
    if(o1.compareTo(o2)> 0)
      return o1;
    else
      return o2;
  }
}

Jika Anda memanggil metode max menggunakan

Max1.max("JAVA itu Tangguh!", 23); // 23 diautobox menjadi new Integer(23)

maka error kompilasi akan ditampilkan, karena dua argumen metode max di dalam Max1 harus bertipe sama (dua objek string atau dua objek integer). Dan, tipe E harus merupakan subtipe dari Comparable<E>.

Contoh lain, dalam kode berikut Anda bisa mendeklarasikan suatu tipe mentah tumpukan pada baris 1, menugaskan new GenericStack<String> kepada tumpukan pada baris 2, dan mempush suatu objek string dan suatu objek integer ke tumpukan pada baris 3 dan 4.

1 GenericStack tumpukan;
2 tumpukan = new GenericStack<String>();
3 tumpukan.push("JAVA itu Tangguh!");
4 tumpukan.push(new Integer(2));

Baris 4 tidak aman karena tumpukan dimaksudkan untuk menyimpan string, tetapi suatu objek Integer ditambahkan ke tumpukan. Baris 3 tidak masalah, tetapi kompiler akan menampilkan pesan pada kedua baris 3 dan 4, karena kompiler tidak bisa mengikuti arti semantik dari program. Semua kompiler mengetahui bahwa tumpukan adalah tipe mentah, jadi beberapa operasi tidak aman untuk dilakukan. Oleh karena itu, peringatan ditampilkan untuk mengingatkan masalah yang potensial terjadi.


7.6 Tipe Generik Wildcard
Apa itu tipe generik wildcard dan mengapa dibutuhkan? Kode7.6 menyajikan suatu contoh untuk mendemonstrasikan kebutuhan tersebut. Contoh ini juga mendefinisikan metode generik max untuk mencari angka maksimum dari tumpukan angka (baris 12-22). Metode utama menciptakan suatu tumpukan objek integer, menambahkan tiga integer pada tumpukan, dan memanggil metode max untuk menemukan angka maksimum di dalam tumpukan.

Kode7.6 DemoWildCard1.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DemoWildCard1 {
  public static void main(String[] args ) {
    GenericStack<Integer> tumpukanInt = new GenericStack<Integer>();
    tumpukanInt.push(1); // 1 diautobox menjadi new Integer(1)
    tumpukanInt.push(2);
    tumpukanInt.push(-2);

    System.out.print("Angka maksimum adalah " + max(tumpukanInt));
  }

  /** Mencari angka maksimum di dalam tumpukan */
  public static double max(GenericStack<Number> tumpukan) {
    double max = tumpukan.pop().doubleValue(); // Inisialisasi max

    while (!tumpukan.isEmpty()) {
      double nilai = tumpukan.pop().doubleValue();
      if (nilai > max)
        max = nilai;
    }

    return max;
  }
}

Program pada kode7.6 memiliki error kompilasi pada baris 8 karena tumpukanInt bukan merupakan instans dari GenerikStack<Number>. Jadi, Anda tidak bisa memanggil max(tumpukanInt).

Fakta bahwa Integer merupakan subtipe dari Number, tetapi GenericStack<Integer> bukan subtipe dari GenericStack<Number>. Untuk mengatasi masalah ini, digunakan tipe generik wildcard. Tipe generik wildcard memiliki tiga bentuk: ?, ? extends T, atau ? super T, dimana T adalah suatu tipe generik.

Bentuk pertama, ?, disebut dengan wildcard tidak terkekang, yang sama dengan ? extends Object. Bentuk kedua, ? extends T, disebut dengan wildcard terkekang, merepresentasikan T atau subtipe dari T yang tidak dikenal. Bentuk ketiga, ? super T, disebut dengan wildcard terkekang-rendah, menandai T atau supertipe dari T yang tidak dikenal.

Anda bisa membetulkan error dengan mengganti baris 12 pada kode7.6 dengan:

public static double max(GenericStack<? extends Number> stack) {

<? extends Number> merupakan suatu tipe wildcard yang merepresentasikan Number atau subtipe dari Number. Jadi, adalah legal untuk memanggil max(new GenericStack<Integer>()) atau max(new GenericStack<Double>()).

Kode7.7 menunjukkan suatu contoh penggunaan wildcard ? di dalam metode tampil yang menampilkan objek-objek di dalam tumpukan dan yang mengosongkan tumpukan. <?> merupakan suatu wildcard yang merepresentasikan sembarang tipe objek. Hal ini ekivalen dengan <? extends Object>. Apa yang terjadi jika Anda mengganti GenericStack<?> dengan GenericStack<Object>? Akan menjadi salah bila memanggil tampil(tumpukanInt), karena tumpukanInt bukan merupakan instans dari GenericStack<Object>. Perhatikan bahwa GenericStack<Integer> bukan merupakan subtipe dari GenericStack<Object>, meskipun Integer merupakan subtipe dari Object.

Kode7.7 DemoWildCard2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DemoWildCard2 {
  public static void main(String[] args ) {
    GenericStack<Integer> tumpukanInt = new GenericStack<Integer>();
    tumpukanInt.push(1); // 1 diautobox menjadi new Integer(1)
    tumpukanInt.push(2);
    tumpukanInt.push(-2);

    tampil(tumpukanInt);
  }

  /** Menampilkan objek dan mengosongkan tumpukan */
  public static void tampil(GenericStack<?> tumpukan) {
    while (!tumpukan.isEmpty()) {
      System.out.print(tumpukan.pop() + " ");
    }
  }
}

Keluaran

-2 2 1

Kapan wildcard <? super T> dibutuhkan? Perhatikan contoh pada kode7.8. Contoh tersebut menciptakan suatu tumpukan string di dalam tumpukan1 (baris 3) dan suatu tumpukan objek di dalam tumpukan2 (baris 4) dan memanggil add(tumpukan1, tumpukan2) (baris 8) untuk menambahkan di dalam tumpukan1 ke tumpukan2. GenericStack<? super T> digunakan untuk mendeklarasikan tumpukan2 pada baris 13. Jika <? super T> digantikan dengan <T>, maka error kompilasi akan terjadi pada add(tumpukan1, tumpukan2) (baris 8), karena tipe tumpukan1 adalah GenericStack<String> dan tipe tumpukan2 adalah GenericStack<Object>. <? super T> merepresentasikan tipe T atau supertipe dari T. Object merupakan supertipe dari String.

Kode7.8 DemoWildCard3.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DemoWildCard3 {
  public static void main(String[] args) {
    GenericStack<String> tumpukan1 = new GenericStack<String>();
    GenericStack<Object> tumpukan2 = new GenericStack<Object>();
    tumpukan2.push("JAVA");
    tumpukan2.push(2);
    tumpukan1.push("Tangguh");
    add(tumpukan1, tumpukan2);
    DemoWildCard2.tampil(tumpukan2);
  }

  public static <T> void add(GenericStack<T> tumpukan1,
     GenericStack<? super T> tumpukan2) {
    while (!tumpukan1.isEmpty())
      tumpukan2.push(tumpukan1.pop());
  }
}

Keluaran

Tangguh 2 JAVA

Relasi pewarisan yang melibatkan tipe generik dan tipe wildcard disimpulkan pada Gambar 7.6. Pada gambar ini, A dan B merepresentasikan kelas dan antarmuka, dan E merepresentasikan parameter tipe generik.

Gambar 7.6 Relasi pewarisan antara tipe generik dan tipe wildcard


7.7 Penghapusan dan Pembatasan pada Generik
Generik diimplementasikan menggunakan suatu pendekatan yang disebut dengan penghapusan tipe. Kompiler menggunakan informasi tipe generik untuk mengkompilasi kode, tetapi menghapusnya setelah itu. Jadi, informasi generik tidak tersedia pada saat runtime. Pendekatan ini memampukan kode generik agar kompatibel mundur dengan kode yang menggunakan tipe mentah.

Generik tersedia pada saat waktu kompilasi. Begitu kompiler mengkonfirmasi bahwa suatu tipe generik aman untuk digunakan, kompiler mengkonversinya menjadi suatu tipe mentah. Sebagai contoh, kompiler memeriksa apakah generik digunakan secara benar atau tidak di dalam kode (a) dan menterjemahkannya menjadi kode ekivalen (b) pada saat runtime. Kode pada (b) menggunakan tipe mentah.


Ketika kelas, antarmuka, dan metode generik dikompilasi, kompiler mengganti tipe generik dengan tipe Object. Sebagai contoh, kompiler akan mengkonversi metode (a) menjadi (b):


Jika tipe generik terkekang, maka kompiler menggantikannya dengan tipe terkekang. Sebagai contoh, kompiler akan mengkonversi metode (a) menjadi (b):



Adalah hal penting untuk memperhatikan bahwa suatu kelas generik dibagi bersama oleh semua instans tanpa memandang tipe konkrit aktual. Dimisalkan daftar1 dan daftar2 diciptakan sebagai berikut:

ArrayList<String> daftar1 = new ArrayList<String>();
ArrayList<Integer> daftar2 = new ArrayList<Integer>();

Meskipun ArrayList<String> dan ArrayList<Integer> adalah dua tipa pada waktu kompilasi, tetapi hanya satu kelas ArrayList yang dimuat ke JVM pada waktu kompilasi. daftar1 dan daftar2 keduanya merupakan instans dari ArrayList. Jadi, dua statemen berikut menampilkan true:

System.out.println(daftar1 instanceof ArrayList);
System.out.println(daftar2 instanceof ArrayList);

Tetapi ekspresi daftar1 instanceof ArrayList<String> adalah salah. Karena ArrayList<String> tidak disimpan sebagai kelas terpisah dalam JVM, penggunaannya pada saat runtime menjadi tidak masuk akal.

Karena tipe generik dihapus pada saat runtime, maka terdapat beberapa pembatasan tertentu tentang bagaimana tipe-tipe generik digunakan. Berikut adalah beberapa pembatasan tersebut.

Pembatasan 1: Tidak bisa menggunakan new E()
Anda tidak bisa menciptakan suatu instans menggunakan suatu parameter tipe generik. Sebagai contoh, statemen berikut adalah salah:

E objek = new E();

Alasannya adalah bahwa new E() dieksekusi pada saat runtime, tetapi tipe generik E tidak tersedia pada saat runtime.
Pembatasan 2: Tidak bisa menggunakan new E[]
Anda tidak bisa menciptakan suatu array menggunakan suatu parameter tipe generik. Sebagai contoh, statemen berikut adalah salah:

E[] elemen = new E[kapasitas]

Anda bisa mengatasi pembatasa ini dengan menciptakan suatu array bertipe Object dan kemudian melakukan casting terhadapnya menjadi E[], sebagai berikut:

E[] elemen = (E[]) new Object[kapasitas];

Bagaimanapun, casting menjadi (E[]) akan menyebabkan peringatan kompilasi. Peringatan terjadi karena kompiler tidak yakin bahwa casting tersebut berhasil pada saat runtime. Sebagai contoh, jika E adalah String dan new Object[] merupakan array yang memuat objek-objek Integer, maka (String[]) (new Object[]) akan menyebabkan suatu ClassCastException. Tipe peringatan kompilasi ini merupakan keterbatasan pemerograman generik JAVA dan merupakan suatu hal yang tidak bisa dihindari.

Penciptaan array generik menggunakan suatu kelas generik tidak diijinkan. Sebagai contoh, kode berikut adalah salah:

ArrayList<String>[] daftar = new ArrayList<String>[10];

Anda bisa menggunakan kode berikut untuk mengatasi keterbatasan ini:

ArrayList<String>[] daftar = (ArrayList<String>[]) new ArrayList[10];

Anda hanya akan mendapatkan peringatan kompilasi.

Pembatasan 3: Parameter tipe generik suatu kelas tidak diijinkan di dalam konteks statik
Karena semua instans dari suatu kelas generik memiliki kelas runtime yang sama, maka variabel dan metode statik dari suatu kelas generik dipakai bersama oleh semua instansnya. Oleh karena itu, adalah hal ilegal untuk mereferensi suatu parameter tipe generik untuk suatu kelas di dalam metode, bidang, atau penginisialisasi statik. Sebagai contoh, kode berikut adalah ilegal:

public class Test<E> {
  public static void m(E o1) { // Ilegal
  }
 
  public static E o1; // Ilegal
 
  static {
    E o2; // Ilegal
  }
}

Pembatasan 4: Kelas eksepsi tidak bisa menjadi generik
Suatu kelas generik tidak bisa mewarisi java.lang.Throwable, jadi deklarasi kelas berikut adalah ilegal:
public class EksepsiKu<T> extends Exception {
}

Mengapa? Jika diijinkan, Anda akan memiliki klausa catch untuk EksepsiKu<T> sebagai berikut:

try {
  ...
}
catch (EksepsiKu<T> ex) {
  ...
}

JVM harus memeriksa eksepsi yang dilemparkan dari klausa try untuk melihat apakah cocok dengan tipe yang dispesifikasi di dalam klausa catch. Hal ini tidak memungkinkan karena tipe informasi tidak tersedia pada saat runtime.

7.8 Studi Kasus: Kelas Matriks Generik
Bagian ini akan menyajikan studi kasus mengenai perancangan kelas untuk operasi-operasi matriks menggunakan tipe generik. Operasi penjumlahan dan perkalian matriks adalah sama kecuali bahwa tipe elemen yang berbeda. Oleh karena itu, Anda dapat merancang suatu superkelas yang mendeskripsikan operasi-operasi umum yang dipakai bersama oleh matriks tanpa memandang tipe elemennya, dan Anda bisa menciptakan beberapa subkelas yang terikat dengan tipe matriks tertentu. Studi kasus ini menyajikan implementasi untuk dua tipe: int dan Rasional. Untuk tipe int, kelas wrapper Integer harus digunakan untuk mengkonversi suatu nilai int menjadi objek, sehingga objek tersebut dilewatkan di dalam metode untuk operasi-operasi matriks.

Diagram kelas ditampilkan pada Gambar 7.7. Metode addMatrix dan multiplyMatrix menjumlahkan dan mengalikan dua matriks bertipe E[][]. Metode statik printResult menampilkan matriks, operasi, dan hasilnya. Metode add, multiply, dan zero adalah abstrak, karena implementasinya tergantung dari tipe spesifik elemen array. Sebagai contoh, metode zero() mengembalikan 0 untuk tipe Integer dan 0/1 untuk tipe Rasional. Metode-metode ini diimplementasikan di dalam sub-subkelas dimana tipe elemen matriks ditentukan.


Gambar 7.7 Kelas GenericMatrix merupakan suatu superkelas abstrak untuk IntegerMatrix dan RasionalMatrix

IntegerMatrix dan RasionalMatrix merupakan sub-subkelas konkrit dari GenericMatrix. Keduanya mengimplementasikan metode add, multiply, dan zero yang didefinisikan di dalam GenericMatrix.

Kode7.9 mengimplementasikan GenericMatrix. <E extends Number> pada baris 3 menyatakan bahwa tipe generik merupakan subtipe dari Number. Tiga metode abstrak add, multimply, dan zero didefinisikan pada baris 3, 6, dan 9. Ketiganya abstrak karena Anda tidak bisa mengimplementasikannya tanpa mengetahui tipe eksak elemen. Metode addMatrix (baris 12-30) dan multiplyMatrix (baris 33-57) mengimplementasikan metode-metode untuk menjumlahkan dan mengalikan dua matriks.  Semua metode tersebut adalah nonstatik, karena menggunakan tipe generik E untuk kelas. Metode printResult (baris 59-84) adalah statik, karena tidak diikat dengan instans spesifik.

Tipe elemen matriks adalah generik. Ini memampukan Anda untuk menggunakan objek dari sembarang kelas sepanjang Anda bisa mengimplementasikan metode-metode abstrak add, multiply, dan zero di dalam sub-subkelas.

Metode addMatrix dan multiplyMatrix (baris 12-57) merupakan metode konkrit. Keduanya siap untuk digunakan sepanjang metode-metode add, multiply, dan zero diimplementasikan di dalam sub-subkelas.

Metode addMatrix dan multiplyMatrix memeriksa batas matriks sebelum melakukan operasi. Jika dua matriks memiliki batas yang tidak kompatibel, maka program akan melemparkan eksepsi (baris 16, 36).

Kode7.9 GenericMatrix.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public abstract class GenericMatrix<E extends Number> {
  /** Metode abstrak untuk menjumlahkan dua elemen dari matriks */
  protected abstract E add(E o1, E o2);

  /** Metode abstrak untuk mengalikan dua elemen dari matriks */
  protected abstract E multiply(E o1, E o2);

  /** Metode abstrak untuk mendefinisikan nol untuk elemen matriks */
  protected abstract E zero();

  /** Menjumlahkan dua matriks */
  public E[][] addMatrix(E[][] matrix1, E[][] matrix2){
    // Memeriksa batas dua matriks
    if ((matrix1.length != matrix2.length) ||
        (matrix1[0].length != matrix2.length)) {
      throw new RuntimeException(
        "Matriks tidak memiliki dimensi yang sama");
    }

    E[][] result =
     (E[][])new Number[matrix1.length][matrix1[0].length];

    // Melakukan penjumlahan
    for (int i = 0; i < result.length; i++)
      for (int j = 0; j < result[i].length; j++) {
        result[i][j] = add(matrix1[i][j], matrix2[i][j]);
      }

    return result;
  }

  /** Mengalikan dua matriks */
  public E[][] multiplyMatrix(E[][] matrix1, E[][] matrix2){
    // Memeriksa batas
    if (matrix1[0].length != matrix2.length) {
      throw new RuntimeException(
        "Matriks tidak memiliki dimensi yang sama");
    }

    // Menciptakan matriks result
    E[][] result =
      (E[][])new Number[matrix1.length][matrix2[0].length];

    // Melakukan perkalian dua matriks
    for (int i = 0; i < result.length; i++) {
      for (int j = 0; j < result[0].length; j++) {
        result[i][j] = zero();

        for (int k = 0; k < matrix1[0].length; k++) {
          result[i][j] = add(result[i][j],
            multiply(matrix1[i][k], matrix2[k][j]));
        }
      }
    }

    return result;
  }

  /** Menampilkan matriks, operator, dan hasil */
  public static void printResult(
      Number[][] m1, Number[][] m2, Number[][] m3, char op){
    for (int i = 0; i < m1.length; i++) {
      for (int j = 0; j < m1[0].length; j++)
        System.out.print(" " + m1[i][j]);

      if (i == m1.length / 2)
        System.out.print( " " + op + " " );
      else
        System.out.print( " " );

      for (int j = 0; j < m2.length; j++)
        System.out.print(" " + m2[i][j]);

      if (i == m1.length / 2)
        System.out.print( " = " );
      else
        System.out.print( " " );

      for (int j = 0; j < m3.length; j++)
        System.out.print(m3[i][j] + " ");

      System.out.println();
    }
  }
}


Kode7.10 mengimplementasikan kelas IntegerMatrix. Kelas ini mewarisi GenericMatrix<Integer> pada baris 1. Setelah instansiasi generik, metode add di dalam GenericMatrix<Integer> sekarang adalah Integer add(Integer o1, Integer o2). Metode add, multiply, dan zero diimplementasikan untuk objek-objek Integer. Ketiga metode tersebut diproteksi, karena ketiganya dipanggil hanya dengan metode addMatrix dan multiplyMatrix.

Kode7.10 IntegerMatrix.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IntegerMatrix extends GenericMatrix<Integer>{
  /** Mengimplementasikan metode add untuk menjumlahkan dua elemen matriks */
  protected Integer add(Integer o1, Integer o2){
   return o1 + o2;
  }

  /** Mengimplementasikan metode multiply untuk mengalikan
    * dua elemen matriks */
  protected Integer multiply(Integer o1, Integer o2){
    return o1 * o2;
  }

  /** Mengimplementasikan metode zero untuk menetapkan nol pada Integer */
  protected Integer zero() {
    return 0;
  }
}

Kode7.11 mengimplementasikan kelas RasionalMatrix. Kelas ini mewarisi GenericMatrix<Rasional> pada baris 1. Setelah instansiasi generik, metode add di dalam GenericMatrix<Rasional> sekarang adalah Rasinal add(Rasinal o1, Rasinal o2). Metode add, multiply, dan zero diimplementasikan untuk objek-objek Rasional. Ketiga metode tersebut diproteksi, karena ketiganya dipanggil hanya dengan metode addMatrix dan multiplyMatrix.

Kode7.11 RasionalMatrix.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RasionalMatrix extends GenericMatrix<Rasional>{
  /** Mengimplementasikan metode add untuk menjumlahkan dua elemen rasional */
  protected Rasional add(Rasional r1, Rasional r2){
    return r1.add(r2);
  }

  /** Mengimplementasikan metode multiply untuk mengalikan
    * dua elemen rasional */
  protected Rasional multiply(Rasional r1, Rasional r2){
    return r1.multiply(r2);
  }

  /** Mengimplementasikan metode zero untuk menetapkan nol pada Rasional */
  protected Rasional zero(){
    return new Rasional(0,1);
  }
}

Kode7.12 menyajikan suatu program yang menciptakan dua matriks Integer (baris 4-5) dan suatu objek IntegerMatrix (baris 8), dan menjumlahkan dan mengalikan dua matriks pada baris 12 dan 16.

Kode7.12 TestIntegerMatrix.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestIntegerMatrix {
  public static void main(String[] args) {
    // Menciptakan array Integer m1, m2
    Integer[][] m1 = new Integer[][]{{1, 2, 3}, {4, 5, 6}, {1, 1, 1}};
    Integer[][] m2 = new Integer[][]{{1, 1, 1}, {2, 2, 2}, {0, 0, 0}};

    // Menciptakan suatu instans dari IntegerMatrix
    IntegerMatrix integerMatrix = new IntegerMatrix();

    System.out.println("\nm1 + m2 adalah ");
    GenericMatrix.printResult(
      m1, m2, integerMatrix.addMatrix(m1, m2), '+');

    System.out.println("\nm1 * m2 adalah ");
    GenericMatrix.printResult(
      m1, m2, integerMatrix.multiplyMatrix(m1, m2), '*');
  }
}

Keluaran

m1 + m2 adalah
 1 2 3    1 1 1   2 3 4
 4 5 6 +  2 2 2 = 6 7 8
 1 1 1    0 0 0   1 1 1
m1 * m2 adalah
 1 2 3    1 1 1    5  5  5
 4 5 6 *  2 2 2 = 14 14 14
 1 1 1    0 0 0    3  3  3

Kode7.13 menyajikan suatu program yang menciptakan dua matriks Rasional (baris 4-10) dan suatu objek RasionalMatrix (baris 13), dan menjumlahkan dan mengalikan dua matriks pada baris 17 dan 21.

Kode7.13 TestRasionalMatrix.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestRasionalMatrix {
  public static void main(String[] args) {
    // Menciptakan dua array Rasional m1 dan m2
    Rasional[][] m1 = new Rasional[3][3];
    Rasional[][] m2 = new Rasional[3][3];
    for (int i = 0; i < m1.length; i++)
      for (int j = 0; j < m1[0].length; j++) {
        m1[i][j] = new Rasional(i + 1, j + 5);
        m2[i][j] = new Rasional(i + 1, j + 6);
      }

    // Mencitpakan suatu instans dari RasionalMatrix
    RasionalMatrix rationalMatrix = new RasionalMatrix();

    System.out.println("\nm1 + m2 adalah ");
    GenericMatrix.printResult(
      m1, m2, rationalMatrix.addMatrix(m1, m2), '+');

    System.out.println("\nm1 * m2 adalah ");
    GenericMatrix.printResult(
      m1, m2, rationalMatrix.multiplyMatrix(m1, m2), '*');
  }
}

Keluaran

m1 + m2 adalah
 1/5 1/6 1/7    1/6 1/7 1/8   11/30 13/42 15/56
 2/5 1/3 2/7 +  1/3 2/7 1/4 = 11/15 13/21 15/28
 3/5 1/2 3/7    1/2 3/7 3/8   11/10 13/14 45/56

m1 * m2 adalah
 1/5 1/6 1/7    1/6 1/7 1/8   101/630 101/735 101/840
 2/5 1/3 2/7 *  1/3 2/7 1/4 = 101/315 202/735 101/420
 3/5 1/2 3/7    1/2 3/7 3/8   101/210 101/245 101/280






No comments:

Post a Comment