Type parameter tanpa syarat menerima tipe apa pun — dan justru karena itulah kemampuannya terbatas. Compiler javac hanya mengizinkan pemanggilan method yang pasti dimiliki semua objek, yakni method milik Object seperti toString dan equals. Method doubleValue() milik Number, compareTo() milik Comparable, atau length() milik String tidak dapat dipanggil pada type parameter bebas karena compiler tidak bisa menjamin method tersebut ada. Bounded type parameter menyelesaikan kebuntuan ini: klausa T extends TipeBatas sekaligus mempersempit tipe yang boleh masuk dan membuka akses ke seluruh member milik tipe batas. Pada Chapter ini kita terapkan bound pada generic method dan generic class, lalu kenali bentuk lanjutannya: multiple bounds.

Method Generik dengan Bound extends Number #

Kasus klasiknya adalah operasi statistik: menjumlahkan isi daftar angka apa pun — List<Integer>, List<Double>, List<Long>. Kita perlu method doubleValue() untuk mengubah setiap elemen menjadi double, dan method tersebut hanya dimiliki turunan Number. Deklarasi <T extends Number> menjadi kuncinya:

java RataRata.java
import java.util.List;

public class RataRata {
    // Bound "extends Number" membuka akses ke method milik Number,
    // sekaligus membatasi T agar hanya angka yang sah masuk.
    static <T extends Number> double rataRata(List<T> data) {
        if (data.isEmpty()) {
            return 0.0;
        }
        double total = 0.0;
        for (T nilai : data) {
            total += nilai.doubleValue();   // sah karena T extends Number
        }
        return total / data.size();
    }

    public static void main(String[] args) {
        List<Integer> bulat = List.of(7, 8, 9);
        List<Double> pecahan = List.of(2.5, 3.5, 7.0);

        System.out.printf("Rata-rata bulat  : %.2f%n", rataRata(bulat));
        System.out.printf("Rata-rata pecahan: %.2f%n", rataRata(pecahan));
    }
}

Output yang Dihasilkan:

text
Rata-rata bulat  : 8.00
Rata-rata pecahan: 4.33

Mari kita bedah bagian-bagian kuncinya:

  1. Baris 6 (static <T extends Number> double rataRata(List<T> data)): bound ditempelkan langsung pada type parameter dengan kata kunci extends. Sejak deklarasi ini, T hanya boleh diisi Number atau turunannya.
  2. Baris 12 (nilai.doubleValue()): pemanggilan ini sah karena doubleValue() adalah member Number — compiler membaca bound sebagai jaminan bahwa setiap T pasti memiliki method tersebut.
  3. Baris 21-22 (rataRata(bulat) dan rataRata(pecahan)): kedua pemanggilan lolos pemeriksaan karena Integer dan Double sama-sama turunan Number. Pemanggilan rataRata(List.of("Budi")) akan ditolak dengan String cannot be converted to ... karena String bukan turunan Number.

Bound pada Generic Class #

Bound juga bekerja pada generic class. Class pengukur berikut menerima daftar angka bertipe apa pun dan menghitung rentang serta totalnya:

java WadahAngka.java
import java.util.ArrayList;
import java.util.List;

public class WadahAngka {
    public static void main(String[] args) {
        Meteran<Integer> meteranBulat = new Meteran<>(List.of(10, 25, 5));
        Meteran<Double> meteranPecahan = new Meteran<>(List.of(1.5, 2.5));

        System.out.printf("Rentang bulat  : %.2f%n", meteranBulat.rentang());
        System.out.printf("Rentang pecahan: %.2f%n", meteranPecahan.rentang());
        System.out.printf("Total bulat    : %.2f%n", meteranBulat.total());
    }
}

class Meteran<T extends Number> {
    private final List<T> data;

    Meteran(List<T> data) {
        this.data = new ArrayList<>(data);
    }

    double rentang() {
        double terkecil = Double.MAX_VALUE;
        double terbesar = -Double.MAX_VALUE;
        for (T nilai : data) {
            double angka = nilai.doubleValue();
            terkecil = Math.min(terkecil, angka);
            terbesar = Math.max(terbesar, angka);
        }
        return terbesar - terkecil;
    }

    double total() {
        double jumlah = 0.0;
        for (T nilai : data) {
            jumlah += nilai.doubleValue();
        }
        return jumlah;
    }
}

Output yang Dihasilkan:

text
Rentang bulat  : 20.00
Rentang pecahan: 1.00
Total bulat    : 40.00

Dua hal patut dicermati dari class tersebut. Pertama, baris 23 (class Meteran<T extends Number>) membuat seluruh method di dalam class — rentang() dan total() — ikut menikmati akses ke doubleValue() tanpa bound tambahan per method. Kedua, baris 6-7 (Meteran<Integer> dan Meteran<Double>) menunjukkan type argument yang sah: setiap turunan Number menggantikan T, dan compiler menolak type argument di luar garis keturunan tersebut saat objek dibuat.

Kesalahan Tanpa Bound #

Untuk merasakan bedanya, coba bayangkan rataRata dideklarasikan tanpa bound — <T> double rataRata(List<T> data). Compiler langsung menolak baris nilai.doubleValue() dengan pesan yang kita verifikasi di JDK 25:

text
CekTanpaBound.java:3: error: cannot find symbol
        return nilai.doubleValue() * 2;
                    ^
  symbol:   method doubleValue()
  location: variable nilai of type T
  where T is a type-variable:
    T extends Object declared in method <T>kaliDua(T)

Pesan location: variable nilai of type T memperlihatkan cara compiler menalar: tanpa bound, satu-satunya jaminan untuk T adalah T extends Object (tertulis eksplisit di pesan), sehingga hanya member Object yang dapat dipanggil. Bound mengubah jaminan tersebut dan membuka member milik tipe batas.

Multiple Bounds #

Satu type parameter boleh memikul beberapa batas sekaligus dengan sintaks <T extends Batas1 & Batas2>. Pola umumnya menggabungkan satu class batas dengan satu atau lebih interface, misalnya method yang butuh Number sekaligus Comparable:

java
static <T extends Number & Comparable<T>> T terbesar(T a, T b) {
    return (a.compareTo(b) >= 0) ? a : b;
}

Batas pertama (Number) membuka doubleValue(), batas kedua (Comparable<T>) membuka compareTo() — kedua method dipakai dalam satu body. Pemanggilan terbesar(3, 7) dan terbesar(2.5, 1.5) pada JDK 25 menghasilkan 7 dan 2.5 karena Integer maupun Double memenuhi kedua batas; argument bertipe Number polos akan ditolak karena tidak Comparable.

Urutan batas mengikuti aturan: paling banyak satu class batas dan posisinya wajib pertama, diikuti interface sebanyak yang diperlukan. Menulis <T extends Comparable<T> & Number> ditolak compiler karena batas class harus mendahului interface.

Bound membatasi satu type parameter terhadap tipe lainnya. Struktur data nyata sering butuh lebih dari satu type parameter sekaligus — pasangan kunci-nilai, misalnya. Chapter Multiple Type Parameters memperkenalkan pola Pair<K, V> yang menjadi dasar pemahaman Map.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

RataRata.java

Unduh berkas
java
import java.util.List;

public class RataRata {
    // Bound "extends Number" membuka akses ke method milik Number,
    // sekaligus membatasi T agar hanya angka yang sah masuk.
    static <T extends Number> double rataRata(List<T> data) {
        if (data.isEmpty()) {
            return 0.0;
        }
        double total = 0.0;
        for (T nilai : data) {
            total += nilai.doubleValue();   // sah karena T extends Number
        }
        return total / data.size();
    }

    public static void main(String[] args) {
        List<Integer> bulat = List.of(7, 8, 9);
        List<Double> pecahan = List.of(2.5, 3.5, 7.0);

        System.out.printf("Rata-rata bulat  : %.2f%n", rataRata(bulat));
        System.out.printf("Rata-rata pecahan: %.2f%n", rataRata(pecahan));
    }
}

WadahAngka.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;

public class WadahAngka {
    public static void main(String[] args) {
        Meteran<Integer> meteranBulat = new Meteran<>(List.of(10, 25, 5));
        Meteran<Double> meteranPecahan = new Meteran<>(List.of(1.5, 2.5));

        System.out.printf("Rentang bulat  : %.2f%n", meteranBulat.rentang());
        System.out.printf("Rentang pecahan: %.2f%n", meteranPecahan.rentang());
        System.out.printf("Total bulat    : %.2f%n", meteranBulat.total());
    }
}

class Meteran<T extends Number> {
    private final List<T> data;

    Meteran(List<T> data) {
        this.data = new ArrayList<>(data);
    }

    double rentang() {
        double terkecil = Double.MAX_VALUE;
        double terbesar = -Double.MAX_VALUE;
        for (T nilai : data) {
            double angka = nilai.doubleValue();
            terkecil = Math.min(terkecil, angka);
            terbesar = Math.max(terbesar, angka);
        }
        return terbesar - terkecil;
    }

    double total() {
        double jumlah = 0.0;
        for (T nilai : data) {
            jumlah += nilai.doubleValue();
        }
        return jumlah;
    }
}