Tipe dengan Batasan

Membatasi parameter tipe dengan T extends Comparable dan menggabungkan lebih dari satu batasan.

Daftar isi

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.

java Contoh: pemanggilan compareTo tanpa batasan
// 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 Object

Padahal 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.

java UjiNilaiMaks.java
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;
    }
}
text Keluaran
Nilai maksimum : 92
Kata maksimum  : Senin

Metode 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.

Parameter tipe T hanya menerima tipe yang memenuhi batasan ComparableT extends Comparable<T>memenuhimemenuhiditolakIntegerComparable<Integer>StringComparable<String>Objectbukan Comparable
Parameter tipe T hanya menerima tipe yang memenuhi batasan Comparable<T>

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.

java UjiTerbaik.java
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);
    }
}
text Keluaran
Pemain terbaik : Bima
Total skor     : 88

Metode 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 super sebagai batasan parameter tipe seperti <T super Number>. Batasan hanya mengenal extends, sedangkan ? super hanya 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 Comparable tanpa 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. Tulis Comparable<T> agar perbandingan terikat pada sesama T.
  • Mengira setiap tipe yang mengimplementasikan Comparable otomatis memenuhi T extends Comparable<T>. Tipe yang implementasinya berupa Comparable<TipeLain> ditolak karena bentuknya tidak cocok, dan pesannya menyebut batasan T yang tidak terpenuhi. Batasan Comparable<T> menuntut pembandingan dengan tipenya sendiri.

Ringkasan#

  • Batasan extends memperluas kemampuan T dari hanya Object menjadi kemampuan batasannya, misalnya memanggil compareTo atas T 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#