Generics (Java 5+) cho phép viết class/method với type parameter — type-safe lúc compile mà không cần cast tay.
List<String> list = new ArrayList<>();
list.add("hi");
String s = list.get(0); // không cần cast
list.add(123); // compile ERRORType erasure: compiler xoá type parameter sau khi check xong → bytecode chỉ có raw type. List<String> và List<Integer> runtime đều là List. Lý do: backward compat với code Java 1.4. Khác C# (reified generics).
Hậu quả của erasure:
if (obj instanceof List<String>) { } // ❌ — chỉ instanceof List<?>
T[] arr = new T[10]; // ❌ không tạo array generic
void f(List<String> l) { }
void f(List<Integer> l) { } // ❌ cùng erasure
static T value; // ❌ không static field genericBounded: <T extends Number> — T phải là Number hoặc subtype.
Wildcards — PECS (Producer Extends, Consumer Super):
void copy(List<? extends Number> src) { // ĐỌC OK, ghi KHÔNG
Number n = src.get(0);
}
void add(List<? super Integer> dest) { // GHI OK, đọc trả Object
dest.add(1);
}- Cần đọc →
<? extends T>(producer). - Cần ghi →
<? super T>(consumer). - Cần cả 2 →
<T>exact.
Workaround: truyền Class<T> hoặc TypeReference (Jackson) để giữ type runtime.
Generics (Java 5+) let you write classes/methods with type parameters — compile-time type-safe without manual casting.
List<String> list = new ArrayList<>();
list.add("hi");
String s = list.get(0); // no cast needed
list.add(123); // compile ERRORType erasure: the compiler erases type parameters after checking → bytecode only has raw types. List<String> and List<Integer> are both List at runtime. Reason: backward compatibility with Java 1.4. Unlike C# (reified generics).
Consequences of erasure:
if (obj instanceof List<String>) { } // ❌ — only instanceof List<?>
T[] arr = new T[10]; // ❌ cannot create generic arrays
void f(List<String> l) { }
void f(List<Integer> l) { } // ❌ same erasure
static T value; // ❌ no generic static fieldsBounded: <T extends Number> — T must be Number or a subtype.
Wildcards — PECS (Producer Extends, Consumer Super):
void copy(List<? extends Number> src) { // READ OK, write NOT
Number n = src.get(0);
}
void add(List<? super Integer> dest) { // WRITE OK, read returns Object
dest.add(1);
}- Need to read →
<? extends T>(producer). - Need to write →
<? super T>(consumer). - Need both → exact
<T>.
Workaround: pass Class<T> or TypeReference (Jackson) to keep the type at runtime.