Parameter tipe tanpa batasan hanya menjanjikan kemampuan kelas Object, karena Object satu-satunya yang diketahui kompilator tentang T. Topik ini menambahkan batasan (bounded type parameter) dengan kata kunci extends, memanfaatkannya pada metode pencarian nilai maksimum, lalu menggabungkan lebih dari satu batasan dalam satu deklarasi. Setelah menyelesaikannya, metode generic dapat memanggil metode milik batasannya dengan aman.
Keterbatasan parameter tipe tanpa batasan#
Di dalam metode generic tanpa batasan, nilai bertipe T hanya boleh diperlakukan sebagai Object. Aturan itu logis dari sisi kompilator, karena metode tersebut harus tetap terkompilasi untuk tipe apa pun, sedangkan satu-satunya kemampuan yang dijamin semua kelas adalah milik Object. Karena itu, menulis data[i].compareTo(...) sama saja dengan memanggil metode yang tidak diketahui ada, dan kompilator menolaknya sebelum program sempat dibangun.
// tidak-dijalankan
static <T> T maks(T[] data) {
T terbesar = data[0];
for (int i = 1; i < data.length; i++) {
if (data[i].compareTo(terbesar) > 0) {
terbesar = data[i];
}
}
return terbesar;
}
// error: cannot find symbol
// symbol: method compareTo(T)
// location: class ObjectPadahal kebutuhan semacam itu sering muncul. Metode pencarian nilai terbesar pada array harus membandingkan dua nilai, dan perbandingan itu baru mungkin bila T punya compareTo. Kelas Integer dan String keduanya mengimplementasikan interface Comparable, yaitu kontrak perbandingan dengan metode compareTo yang mengembalikan bilangan negatif, nol, atau positif tergantung urutan dua nilainya. Yang dibutuhkan adalah cara menyatakan bahwa T wajib mengikuti kontrak tersebut, sehingga kompilator berhenti menuntut kemampuan yang tidak dijamin dan mulai menjamin kemampuan yang dipinta.
Mendeklarasikan batasan dengan extends#
Batasan ditulis dengan extends setelah parameter tipe, misalnya <T extends Comparable<T>>. Sejak batasan itu dipasang, kompilator memperlakukan setiap nilai T sebagai Comparable, sehingga compareTo boleh dipanggil. Argumen Comparable<T> memang terasa berputar ke dirinya sendiri, tetapi artinya tepat, yaitu T harus bisa dibandingkan dengan sesama T. Metode berikut mencari nilai maksimum array secara aman untuk Integer, String, maupun tipe lain yang bisa dibandingkan.
public class UjiNilaiMaks {
public static void main(String[] args) {
Integer[] nilai = {67, 89, 75, 92, 81};
String[] kata = {"Rabu", "Senin", "Jumat"};
System.out.println("Nilai maksimum : " + maks(nilai));
System.out.println("Kata maksimum : " + maks(kata));
}
static <T extends Comparable<T>> T maks(T[] data) {
T terbesar = data[0];
for (int i = 1; i < data.length; i++) {
if (data[i].compareTo(terbesar) > 0) {
terbesar = data[i];
}
}
return terbesar;
}
}Nilai maksimum : 92
Kata maksimum : SeninMetode yang sama melayani dua tipe berbeda tanpa satu pun cast. Untuk Integer, compareTo membandingkan nilai numeriknya, sehingga hasilnya 92. Untuk String, perbandingan berlangsung karakter demi karakter sesuai urutan Unicode, sehingga Senin berada di atas Rabu dan Jumat. Pemanggilan dengan tipe yang tidak memenuhi batasan langsung ditolak kompilator. Pemanggilan maks(new Object[] {new Object(), new Object()}) misalnya gagal dengan alasan inference variable T has incompatible bounds.
Batasan membuat pemilihan tipe menjadi selektif, dan pola itu digambarkan berikut ini.
Menggabungkan lebih dari satu batasan#
Beberapa batasan digabungkan dengan tanda &, dan seluruh batasan wajib dipenuhi oleh tipe yang ingin mengisi T. Contohnya <T extends Skor & Comparable<T>> menuntut tipe tersebut mengimplementasikan Skor sekaligus bisa dibandingkan. Aturan pentingnya, paling banyak satu batasan boleh berupa kelas, dan bila ada, batasan kelas itu harus ditulis lebih dulu, selebihnya interface. Deklarasi <T extends Comparable<T> & KelasOrang> untuk kelas KelasOrang misalnya ditolak dengan pesan interface expected here.
import java.util.List;
public class UjiTerbaik {
public static void main(String[] args) {
List<Pemain> tim = List.of(
new Pemain("Andi", 62),
new Pemain("Bima", 88),
new Pemain("Cika", 75));
Pemain bintang = terbaik(tim);
System.out.println("Pemain terbaik : " + bintang.nama());
System.out.println("Total skor : " + bintang.totalSkor());
}
static <T extends Skor & Comparable<T>> T terbaik(List<T> daftar) {
T terbaikSaatIni = daftar.get(0);
for (T p : daftar) {
if (p.compareTo(terbaikSaatIni) > 0) {
terbaikSaatIni = p;
}
}
return terbaikSaatIni;
}
}
interface Skor {
int totalSkor();
}
record Pemain(String nama, int skor) implements Skor, Comparable<Pemain> {
@Override
public int totalSkor() {
return skor;
}
@Override
public int compareTo(Pemain lain) {
return Integer.compare(this.skor, lain.skor);
}
}Pemain terbaik : Bima
Total skor : 88Metode terbaik memakai kedua batasannya sekaligus. Perulangan memanggil compareTo dari batasan Comparable untuk mencari pemain dengan skor paling besar, sedangkan hasil akhirnya dibaca lewat metode nama() dan totalSkor() milik tipe tersebut. Bila ada tipe yang hanya mengimplementasikan Skor tanpa Comparable, pemanggilan terbaik atasnya langsung ditolak kompilator. Dengan demikian kebutuhan dua kemampuan berbeda itu dinyatakan dalam satu baris deklarasi.
Daftar batasan boleh lebih panjang dari dua, dan isi aturannya tetap sama, yaitu kelas tunggal di depan lalu sisa interface setelah tanda &. Record Pemain pada contoh mengimplementasikan Skor sekaligus Comparable<Pemain>, sehingga ia lolos sebagai pengisi T. Bentuk gabungan ini lazim pada kode utilitas yang menuntut identitas dan perilaku sekaligus, misalnya tipe yang punya nama untuk ditampilkan dan urutan untuk disusun.
Kesalahan umum#
- Memanggil metode milik tipe tertentu atas nilai T tanpa batasan. Kompilator menolaknya karena T hanya menjamin kemampuan Object. Tambahkan batasan yang sesuai, misalnya
T extends Comparable<T>. - Menulis
supersebagai batasan parameter tipe seperti<T super Number>. Batasan hanya mengenalextends, sedangkan? superhanya sah pada wildcard. Perbaiki bentuknya atau ubah desain metodenya. - Menempatkan batasan kelas setelah batasan interface, misalnya
<T extends Comparable<T> & KelasOrang>. Aturannya batasan kelas harus berada paling depan, dan batasan lain mengikuti setelah tanda&. - Memakai
Comparabletanpa argumen tipe pada batasan, yaitu<T extends Comparable>. Deklarasi itu masih terkompilasi, tetapi pemeriksaan tipenya longgar sehingga perbandingan antar tipe yang tidak cocok bisa lolos. TulisComparable<T>agar perbandingan terikat pada sesama T. - Mengira setiap tipe yang mengimplementasikan Comparable otomatis memenuhi
T extends Comparable<T>. Tipe yang implementasinya berupaComparable<TipeLain>ditolak karena bentuknya tidak cocok, dan pesannya menyebut batasan T yang tidak terpenuhi. BatasanComparable<T>menuntut pembandingan dengan tipenya sendiri.
Ringkasan#
- Batasan
extendsmemperluas kemampuan T dari hanya Object menjadi kemampuan batasannya, misalnya memanggilcompareToatasT extends Comparable<T>. - Bentuk
Comparable<T>menyatakan tipe yang bisa dibandingkan dengan sesamanya, dan pola ini dipakai pada metode seperti pencarian nilai maksimum. - Beberapa batasan digabung dengan
&, paling banyak satu di antaranya berupa kelas, dan kelas itu harus ditulis lebih dulu. - Tipe yang tidak memenuhi batasan ditolak saat kompilasi, bukan saat program berjalan.
Referensi#
- Bounded Type Parameters — Oracle
— panduan resmi parameter tipe berbatasan beserta contoh
T extends Comparable. - §4.4 Type Variables — Oracle — spesifikasi variabel tipe, batasannya, dan aturan penggabungannya.