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