Class Kotak<String> dan Kotak<Integer> terasa seperti saudara — sayangnya compiler javac tidak menganggap demikian. Dalam generics Java, List<Integer> bukan subtype dari List<Number> meskipun Integer adalah subtype dari Number; hubungan tipe parameternya tidak menular ke tipe parametrisasinya. Sifat ini dinamai invariance, dan sifat inilah yang membuat parameter method seperti List<Number> menolak List<Integer> padahal niatnya wajar. Java menjawab dengan wildcard: tanda tanya ? yang mewakili tipe yang tidak kita ketahui. Pada Chapter ini kita bedah invariance lebih dulu, lalu memakai List<?> untuk membaca daftar tipe apa pun — dan melihat mengapa menulis ke daftar tersebut justru dilarang.
Generics Itu Invariant #
Mari mulai dari kegagalannya. Assignment berikut terasa sah secara akal sehat, tetapi ditolak compiler dengan pesan yang kita verifikasi di JDK 25:
List<Integer> bulat = new ArrayList<>();
List<Number> angka = bulat; // errorCekInvarian.java:7: error: incompatible types: List<Integer> cannot be converted to List<Number>
List<Number> angka = bulat;
^Penolakan tersebut melindungi kita dari lubang nyata. Andaikan assignment diperbolehkan, variabel angka bertipe List<Number> akan sah menerima angka.add(2.5) — nilai Double masuk ke daftar yang sesungguhnya berisi Integer. Pembaca berikutnya yang memanggil bulat.get(0) dan meng-cast hasilnya ke Integer akan menerima ClassCastException dari nilai yang tidak pernah ia masukkan. Compiler memilih memutus rantai risiko itu sejak kompilasi: tipe parametrisasi tidak mewarisi relasi subtype milik type argument-nya.
Membaca Daftar dengan Wildcard Tidak Dikenal #
Kebalikan masalah muncul pada method utilitas: bagaimana membuat satu method yang menerima daftar elemen tipe apa pun? Parameter List<Object> tidak membantu — justru karena invariance, List<String> tidak dapat dikonversi ke List<Object>. Wildcard ? menjawab: List<?> berarti “daftar dengan elemen bertipe tertentu yang tidak perlu kita ketahui”:
import java.util.List;
public class PembacaDaftar {
// List<?> menerima daftar dengan elemen tipe apa pun
static void tampilkan(List<?> daftar) {
for (Object elemen : daftar) { // elemen hanya bisa dibaca sebagai Object
System.out.print(elemen + " ");
}
System.out.println("(ukuran " + daftar.size() + ")");
}
public static void main(String[] args) {
List<String> nama = List.of("Budi", "Siti");
List<Integer> angka = List.of(10, 20, 30);
List<Double> pecahan = List.of(1.5, 2.5);
tampilkan(nama);
tampilkan(angka);
tampilkan(pecahan);
}
}Output yang Dihasilkan:
Budi Siti (ukuran 2)
10 20 30 (ukuran 3)
1.5 2.5 (ukuran 2)Mari kita bedah mekanismenya:
- Baris 5 (
static void tampilkan(List<?> daftar)): parameter menerimaList<String>,List<Integer>,List<Double>— bahkanList<Pasangan<String, Integer>>. Wildcard tidak terikat pada hierarki tipe tertentu, sehingga cakupannya paling luas. - Baris 6 (
for (Object elemen : daftar)): elemen hanya boleh dibaca sebagaiObject. Compiler tidak tahu tipe asli elemennya, sehingga satu-satunya jaminan yang bisa diberikan adalah “pasti sebuah objek”. Methodsize(),isEmpty(), danclear()tetap sah karena tidak menyentuh tipe elemen. - Baris 17-19 (tiga pemanggilan): satu definisi method melayani tiga daftar dengan type argument berbeda — inilah fleksibilitas yang tidak dimiliki parameter
List<Object>maupunList<Number>.
Menulis ke List Wildcard Dilarang #
Sisi penulisan cerita sebaliknya: compiler tidak tahu tipe elemen yang sebenarnya, sehingga tidak ada nilai yang bisa dijamin cocok. Program berikut mencoba menambah string:
import java.util.List;
public class TambahWildcards {
static void isi(List<?> daftar) {
daftar.add("halo"); // compiler menolak baris ini
}
public static void main(String[] args) {
isi(List.of("Budi"));
}
}Kompilasi gagal dengan pesan verbatim berikut:
TambahWildcards.java:5: error: no suitable method found for add(String)
method List.add(CAP#1) is not applicable
(argument mismatch; String cannot be converted to CAP#1)
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?Istilah CAP#1 mengungkap mekanisme di balik layar: compiler mengganti ? dengan capture — semacam type parameter segar CAP#1 extends Object yang spesifik untuk satu pemeriksaan. Karena CAP#1 bisa berarti Integer, String, atau tipe lain yang tidak kita ketahui, tidak ada argument (kecuali null literal yang bertipe semua tipe referensi) yang dapat lolos konversi. Larangan menulis inilah harga yang dibayar List<?> demi jaminan keamanan tipe.
Mengapa Wildcard Dibutuhkan #
Kebutuhan paling nyata muncul pada operasi penyalinan. Method sederhana berikut ingin memindahkan isi daftar bilangan bulat ke daftar angka yang lebih umum:
static void salin(List<Number> tujuan, List<Number> sumber) {
tujuan.addAll(sumber);
}
List<Number> angka = new ArrayList<>();
List<Integer> bulat = List.of(1, 2);
salin(angka, bulat); // gagal kompilasiPemanggilan salin(angka, bulat) ditolak dengan error: incompatible types: List<Integer> cannot be converted to List<Number> — persis pesan invariance di awal Chapter, kali ini pada posisi argument. Mengganti parameter menjadi List<Integer> menyelesaikan satu kasus tetapi menutup pemanggilan dengan List<Double>; menggantinya menjadi List<Number> memaksa pemanggil mengonversi daftar secara manual.
Jalan keluarnya bukan satu wildcard polos, melainkan dua bentuk berbatas: ? extends untuk parameter yang dibaca (sumber) dan ? super untuk parameter yang ditulis (tujuan). Keduanya mengikuti prinsip yang kelak kita kenal sebagai PECS. Chapter Upper-Bounded Wildcards membuka bagian pertama dari pasangan tersebut.
Referensi #
- Oracle: Wildcards (Java Tutorials)
- JLS SE25 §4.5.1: Type Arguments and Wildcards
- dev.java: Generics
- Java API: java.util.Collection
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
PembacaDaftar.java
Unduh berkasimport java.util.List;
public class PembacaDaftar {
// List<?> menerima daftar dengan elemen tipe apa pun
static void tampilkan(List<?> daftar) {
for (Object elemen : daftar) { // elemen hanya bisa dibaca sebagai Object
System.out.print(elemen + " ");
}
System.out.println("(ukuran " + daftar.size() + ")");
}
public static void main(String[] args) {
List<String> nama = List.of("Budi", "Siti");
List<Integer> angka = List.of(10, 20, 30);
List<Double> pecahan = List.of(1.5, 2.5);
tampilkan(nama);
tampilkan(angka);
tampilkan(pecahan);
}
}
TambahWildcards.java
Unduh berkasimport java.util.List;
public class TambahWildcards {
static void isi(List<?> daftar) {
daftar.add("halo"); // compiler menolak baris ini
}
public static void main(String[] args) {
isi(List.of("Budi"));
}
}