Wildcard polos ? pada Chapter sebelumnya berhasil menerima daftar tipe apa pun, tetapi kemampuan bacanya rendah: elemen hanya bisa diperlakukan sebagai Object, sehingga method khas angka seperti doubleValue() tidak dapat dipanggil. Untuk method penjumlahan, kompromi tersebut sama sekali tidak memadai. Upper-bounded wildcard mempersempit wildcard dengan batas atas: List<? extends Number> berarti “daftar bertipe tertentu yang pasti Number atau turunannya”. Batas itu menghadirkan dua efek sekaligus — cakupan tetap luas (List<Integer>, List<Double>, List<Long> semuanya masuk) dan elemen dapat dibaca sebagai Number dengan seluruh method-nya tersedia. Pada Chapter ini kita terapkan pola tersebut pada method penjumlahan, membuktikan larangan penulisannya, lalu memperkenalkan mnemonic PECS dari sisi producer.
Menerima Semua Turunan Number #
Method jumlahkan berikut membaca daftar angka apa pun dan menjumlahkannya sebagai double. Tanpa wildcard, parameter List<Number> hanya menerima List<Number> persis — bukan List<Integer> — karena invariance yang kita bahas sebelumnya:
import java.util.List;
public class Penjumlah {
// Producer: daftar hanya dibaca. Bound ? extends Number mengizinkan
// List<Integer>, List<Double>, List<Long>, dan semua turunan Number.
static double jumlahkan(List<? extends Number> angka) {
double total = 0.0;
for (Number nilai : angka) { // dibaca sebagai Number, bukan Object
total += nilai.doubleValue();
}
return total;
}
public static void main(String[] args) {
List<Integer> bulat = List.of(10, 20, 30);
List<Double> pecahan = List.of(1.5, 2.5);
List<Long> panjang = List.of(100L, 200L);
System.out.printf("Bulat : %.1f%n", jumlahkan(bulat));
System.out.printf("Pecahan : %.1f%n", jumlahkan(pecahan));
System.out.printf("Panjang : %.1f%n", jumlahkan(panjang));
}
}Output yang Dihasilkan:
Bulat : 60.0
Pecahan : 4.0
Panjang : 300.0Mari kita bedah bagian-bagian kuncinya:
- Baris 6 (
static double jumlahkan(List<? extends Number> angka)): parameter membaca sebagai “daftar elemen bertipe tak diketahui yang merupakan turunanNumber”. SemuaList<Integer>,List<Double>, danList<Long>dapat masuk tanpa konversi. - Baris 8 (
for (Number nilai : angka)): inilah kenaikan kelas dibandingList<?>— elemen terbaca sebagaiNumber, bukanObject. Compiler menjamin setiap elemen pasti memiliki methoddoubleValue(),intValue(), danlongValue(). - Baris 9 (
nilai.doubleValue()): pemanggilan method milikNumbersah langsung di dalam loop. PadaList<?>, baris identik akan ditolak karenaObjecttidak memilikidoubleValue(). - Baris 19-21 (tiga pemanggilan): satu signature melayani tiga type argument berbeda. Bandingkan dengan versi
List<Number>yang menolak dua pemanggilan pertama, atau versiList<Integer>yang menolak dua pemanggilan terakhir.
Producer yang Tidak Boleh Ditulis #
Fleksibilitas baca ? extends dibayar dengan larangan tulis — sama seperti wildcard polos, tetapi alasannya kini lebih mudah dinalar. Program berikut mencoba menambah Integer ke daftar berbatas:
import java.util.List;
public class TambahBatasAtas {
static void isiNilai(List<? extends Number> daftar) {
daftar.add(42); // ditolak compiler: tipe asli daftar tidak diketahui
}
public static void main(String[] args) {
isiNilai(List.of(1, 2));
}
}Kompilasi gagal dengan pesan verbatim dari JDK 25:
TambahBatasAtas.java:5: error: no suitable method found for add(int)
method List.add(CAP#1) is not applicable
(argument mismatch; int cannot be converted to CAP#1)
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends NumberAlasannya konkret: variabel bertipe List<? extends Number> bisa menunjuk List<Double> yang sebenarnya. Andaikan daftar.add(42) diizinkan, integer akan tersimpan di daftar yang seharusnya hanya memuat Double — dan pemanggil lain yang membaca elemen sebagai Double akan menerima ClassCastException. Compiler javac mengganti ? dengan capture CAP#1 extends Number, lalu menolak setiap penulisan karena tidak ada nilai yang pasti cocok dengan semua kemungkinan CAP#1. Satu-satunya pengecualian adalah null, yang bertipe semua tipe referensi.
PECS Producer Extends #
Kapan ? extends menjadi pilihan tepat? Prinsip PECS memberi rumus ringkas: Producer Extends, Consumer Super. Sebuah koleksi bertindak sebagai producer bila kode kita hanya mengambil nilai darinya — koleksi memproduksi nilai untuk dikonsumsi method. Untuk parameter producer, pakai ? extends. Method jumlahkan adalah producer murni: seluruh interaksinya dengan angka hanya membaca.
Prinsip ini bukan aturan bahasa yang dijaga compiler, melainkan pedoman desain API. Parameter List<Number> menutup pemanggilan dengan List<Integer>; parameter List<? extends Number> membukanya tanpa kehilangan type safety — cukup alasan untuk menjadikannya kebiasaan default pada parameter yang hanya dibaca.
Satu sisi PECS masih kosong: bagaimana dengan parameter yang justru ingin ditulis — diisi nilai oleh method kita? Membatasi dengan ? extends jelas salah arah karena penulisan terlarang. Jawabannya adalah wildcard dengan batas bawah ? super, pasangan consumer dari ? extends, yang kita bahas pada Chapter Lower-Bounded Wildcards.
Referensi #
- Oracle: Upper Bounded Wildcards (Java Tutorials)
- JLS SE25 §4.5.1: Type Arguments and Wildcards
- Java API: java.util.Collections
- dev.java: Generics
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
Penjumlah.java
Unduh berkasimport java.util.List;
public class Penjumlah {
// Producer: daftar hanya dibaca. Bound ? extends Number mengizinkan
// List<Integer>, List<Double>, List<Long>, dan semua turunan Number.
static double jumlahkan(List<? extends Number> angka) {
double total = 0.0;
for (Number nilai : angka) { // dibaca sebagai Number, bukan Object
total += nilai.doubleValue();
}
return total;
}
public static void main(String[] args) {
List<Integer> bulat = List.of(10, 20, 30);
List<Double> pecahan = List.of(1.5, 2.5);
List<Long> panjang = List.of(100L, 200L);
System.out.printf("Bulat : %.1f%n", jumlahkan(bulat));
System.out.printf("Pecahan : %.1f%n", jumlahkan(pecahan));
System.out.printf("Panjang : %.1f%n", jumlahkan(panjang));
}
}
TambahBatasAtas.java
Unduh berkasimport java.util.List;
public class TambahBatasAtas {
static void isiNilai(List<? extends Number> daftar) {
daftar.add(42); // ditolak compiler: tipe asli daftar tidak diketahui
}
public static void main(String[] args) {
isiNilai(List.of(1, 2));
}
}