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:

java Penjumlah.java
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:

text
Bulat   : 60.0
Pecahan : 4.0
Panjang : 300.0

Mari kita bedah bagian-bagian kuncinya:

  1. Baris 6 (static double jumlahkan(List<? extends Number> angka)): parameter membaca sebagai “daftar elemen bertipe tak diketahui yang merupakan turunan Number”. Semua List<Integer>, List<Double>, dan List<Long> dapat masuk tanpa konversi.
  2. Baris 8 (for (Number nilai : angka)): inilah kenaikan kelas dibanding List<?> — elemen terbaca sebagai Number, bukan Object. Compiler menjamin setiap elemen pasti memiliki method doubleValue(), intValue(), dan longValue().
  3. Baris 9 (nilai.doubleValue()): pemanggilan method milik Number sah langsung di dalam loop. Pada List<?>, baris identik akan ditolak karena Object tidak memiliki doubleValue().
  4. Baris 19-21 (tiga pemanggilan): satu signature melayani tiga type argument berbeda. Bandingkan dengan versi List<Number> yang menolak dua pemanggilan pertama, atau versi List<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:

text
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 Number

Alasannya 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 #

Berkas Sumber

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

Penjumlah.java

Unduh berkas
java
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));
    }
}

TambahBatasAtas.java

Unduh berkas
java
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));
    }
}