Sepanjang Section ini kita menikmati kekuatan generics; kini kita petakan batas-batasnya. Semua larangan yang akan kita temui berakar pada satu sebab yang sama: type erasure. Karena informasi type argument dihapus dari bytecode, JVM tidak memiliki data untuk memeriksa T saat runtime — sehingga operasi yang membutuhkan pengetahuan tipe runtime pada type parameter tidak mungkin diizinkan oleh compiler javac. Alih-alih menghafal daftar larangan tanpa makna, kita akan verifikasi setiap pesan error-nya langsung di JDK 25, pahami alasan di baliknya, dan pelajari jalan keluar yang lazim dipakai pustaka standar. Pada akhirnya seluruh larangan tersebut terangkum dalam satu tabel ringkas siap dipakai saat berkoding.
Larangan Membuat Array Tipe Generik #
Ekspresi new T[ukuran] ditolak compiler karena array Java menyimpan tipe komponennya secara nyata di runtime — sementara T justru hilang setelah erasure. Verifikasi di JDK 25:
public class CekArray<T> {
T[] buat(int ukuran) {
return new T[ukuran]; // error
}
}CekArray.java:3: error: generic array creation
return new T[ukuran];
^Jalan keluar baku memakai type token: objek Class<T> dikirim sebagai constructor argument, lalu array dibuat lewat refleksi Array.newInstance. Solusi ini menuntut cast (T[]) yang diberi @SuppressWarnings("unchecked") karena compiler tidak dapat memverifikasinya:
import java.lang.reflect.Array;
class Wadah<T> {
private final T[] data;
Wadah(Class<T> tipe, int kapasitas) {
@SuppressWarnings("unchecked")
T[] hasil = (T[]) Array.newInstance(tipe, kapasitas);
data = hasil;
}
}Mekanisme di baliknya dapat kita amati langsung tanpa cast yang meragukan:
import java.lang.reflect.Array;
import java.util.Arrays;
public class ArrayRefleksi {
public static void main(String[] args) {
// Array dibuat saat runtime: komponen diketahui dari objek Class
String[] nama = (String[]) Array.newInstance(String.class, 3);
nama[0] = "Budi";
nama[1] = "Siti";
nama[2] = "Dewi";
System.out.println(Arrays.toString(nama));
int[] angka = (int[]) Array.newInstance(int.class, 3);
angka[0] = 7;
angka[1] = 8;
angka[2] = 9;
System.out.println(Arrays.toString(angka));
System.out.println(nama.getClass().getComponentType());
System.out.println(angka.getClass().getComponentType());
}
}Output yang Dihasilkan:
[Budi, Siti, Dewi]
[7, 8, 9]
class java.lang.String
intDua baris terakhir keluaran memperlihatkan inti solusinya: getComponentType() melaporkan class java.lang.String untuk array pertama dan int untuk array kedua — tipe komponen benar-benar tercatat pada objek array, hal yang mustahil dilakukan type parameter. Mari kita bedah baris kuncinya:
- Baris 7 (
(String[]) Array.newInstance(String.class, 3)):Array.newInstancemenerima type tokenString.classdan kapasitas, lalu mengembalikanObject. Cast keString[]aman karena type token menjamin tipe komponen, dan JVM tetap memeriksa cast array saat runtime. - Baris 13 (
Array.newInstance(int.class, 3)): type token berupa class primitive —int.class— menghasilkan arrayint[]sungguhan, bukan array wrapper, sehingga bebas boxing. - Baris 19-20 (
getComponentType()): kedua array melaporkan tipe komponennya masing-masing dari sisi runtime — bukti langsung bahwa tipe komponen array hidup di JVM, sementara type parameter tidak.ArrayList<E>memilih jalan berbeda: menyimpanObject[]secara internal lalu meng-cast setiap hasilget— itulah sebabnya pemeriksaan elemenArrayList<String>tetap terjadi di titik baca.
Larangan instanceof dengan Tipe Parametrisasi #
Operator instanceof memeriksa tipe runtime, sedangkan List<String> tidak ada di runtime — pertentangan yang diselesaikan dengan melarang kombinasi keduanya:
Object o = List.of("a");
if (o instanceof List<String> daftar) { // error
System.out.println(daftar.size());
}CekInstanceof.java:6: error: Object cannot be safely cast to List<String>
if (o instanceof List<String> daftar) {
^Pesan di JDK 25 berbunyi Object cannot be safely cast to List<String>; dokumentasi dan tulisan lawas sering mengutip pesan era JDK 8, illegal generic type for instanceof — keduanya menandai larangan yang sama. Batasan ini juga berlaku pada pattern matching untuk instanceof dan switch: pattern bertipe parametrisasi (misalnya case List<String> l) tidak diperbolehkan karena alasan identik. Yang tetap sah adalah wildcard polos:
import java.util.List;
public class CekInstanceofAman {
public static void main(String[] args) {
Object o = List.of("Budi", "Siti");
if (o instanceof List<?> daftar) { // wildcard polos: sah
System.out.println("Ukuran: " + daftar.size());
for (Object elemen : daftar) {
System.out.println("Elemen: " + elemen);
}
}
}
}Output yang Dihasilkan:
Ukuran: 2
Elemen: Budi
Elemen: SitiPattern List<?> lolos karena pemeriksaannya hanya menyentuh class runtime List, bukan type argument-nya — konsekuensinya, elemen hanya terbaca sebagai Object, persis seperti perilaku List<?> pada Chapter Wildcards.
Larangan static Member dengan Type Parameter #
Kata kunci static berarti member milik class, bukan milik objek — sementara type parameter baru mendapat nilainya per instansiasi objek. Field static T akan menjadi ambigu: T milik Kotak<String> atau Kotak<Integer>?
public class CekStatic<T> {
static T cadangan; // error
}CekStatic.java:2: error: non-static type variable T cannot be referenced from a static context
static T cadangan;
^Larangan yang sama berlaku untuk method static yang memakai type parameter class. Jika logika static memang butuh tipe generik, deklarasikan type parameter miliknya sendiri pada method tersebut — pola static <U> ... yang kita pelajari pada Chapter Generic Methods — sehingga U disimpulkan per pemanggilan, bukan per instansiasi.
Larangan Class Exception Generik #
Blok catch mencocokkan exception berdasarkan class runtime. Andaikan Galat<T> extends Exception diizinkan, erasure akan membuat catch (Galat<String>) dan catch (Galat<Integer>) menjadi dua label untuk class runtime yang sama — konflik yang dicegah sejak kompilasi:
public class Galat<T> extends Exception { // error
}CekGalat.java:1: error: a generic class may not extend java.lang.Throwable
public class CekGalat<T> extends Exception {
^Idiom penggantinya adalah komposisi: class exception non-generik yang membawa payload bertipe apa pun di field, misalnya class Galat extends Exception { private final Object muatan; } — dan bila konteks pengguna sempit, subtype konkret seperti GalatString extends Galat menyimpan tipe spesifik tanpa menyentuh pewarisan generik.
Primitive Bukan Type Argument dan Larangan Lain #
Type argument wajib tipe referensi. List<int> ditolak dengan pesan yang menyebut syaratnya secara eksplisit:
CekPrimitif.java:5: error: unexpected type
List<int> angka = null;
^
required: reference
found: intSolusinya adalah wrapper (List<Integer>) dengan biaya boxing, atau spesialisasi seperti IntStream untuk pipeline angka. Satu larangan terakhir kerap mengejutkan: dua overload yang dibedakan hanya oleh type argument ditolak karena hasil erasure-nya identik:
CekBentrok.java:5: error: name clash: tampil(List<Integer>) and tampil(List<String>) have the same erasure
static void tampil(List<Integer> angka) { }
^Berikut rangkuman seluruh larangan beserta alasannya:
| Larangan | Pesan error kunci | Alasan erasure | Jalan keluar lazim |
|---|---|---|---|
new T[...] | generic array creation | tipe komponen array dicek runtime | Array.newInstance + Class<T> |
x instanceof List<String> | Object cannot be safely cast to List<String> | type argument tak ada di runtime | instanceof List<?> |
static T field | non-static type variable T ... | type parameter per instansiasi | type parameter pada method static |
class G<T> extends Throwable | generic class may not extend java.lang.Throwable | catch mencocokkan class runtime | komposisi payload non-generik |
List<int> | unexpected type ... required: reference | type argument wajib referensi | wrapper atau IntStream |
overload List<A> vs List<B> | name clash ... same erasure | signature terhapus identik | bedakan nama method |
Dengan peta rintangan lengkap di tangan, kita siap menuju puncak Section: merakit seluruh konsep — class, method, bound, wildcard, dan batasan erasure — menjadi satu API generik yang enak dipakai dan sulit disalahgunakan. Chapter Merancang Generic API menutup dengan studi kasus perancangan bertahap.
Referensi #
- Oracle: Restrictions on Generics (Java Tutorials)
- JLS SE25 §4.7: Reifiable Types
- Java API: java.lang.reflect.Array.newInstance
- dev.java: Generics
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ArrayRefleksi.java
Unduh berkasimport java.lang.reflect.Array;
import java.util.Arrays;
public class ArrayRefleksi {
public static void main(String[] args) {
// Array dibuat saat runtime: komponen diketahui dari objek Class
String[] nama = (String[]) Array.newInstance(String.class, 3);
nama[0] = "Budi";
nama[1] = "Siti";
nama[2] = "Dewi";
System.out.println(Arrays.toString(nama));
int[] angka = (int[]) Array.newInstance(int.class, 3);
angka[0] = 7;
angka[1] = 8;
angka[2] = 9;
System.out.println(Arrays.toString(angka));
System.out.println(nama.getClass().getComponentType());
System.out.println(angka.getClass().getComponentType());
}
}
CekInstanceofAman.java
Unduh berkasimport java.util.List;
public class CekInstanceofAman {
public static void main(String[] args) {
Object o = List.of("Budi", "Siti");
if (o instanceof List<?> daftar) { // wildcard polos: sah
System.out.println("Ukuran: " + daftar.size());
for (Object elemen : daftar) {
System.out.println("Elemen: " + elemen);
}
}
}
}