Type Erasure và Limitations
Sau bài này, bạn sẽ:
- Hiểu về type erasure - cách compiler xóa bỏ thông tin generic types tại runtime
- Nắm được bridge methods và cách compiler maintain type safety sau erasure
- Biết các limitations do type erasure (không thể
new T(), không dùng primitives, khônginstanceof) - Áp dụng các workarounds như truyền Class objects, dùng Supplier, Array.newInstance
- Phân biệt reifiable vs non-reifiable types và tránh heap pollution
Bài trước: Wildcards và PECS — Đã học về ba loại wildcards và PECS principle. Bài này sẽ giải thích type erasure - lý do tại sao Generics có nhiều limitations.
Type Erasure là gì?
Type Erasure là quá trình compiler xóa bỏ tất cả thông tin về generic types và thay thế bằng raw types hoặc bounds. Điều này xảy ra tại compile time.
Generic type information CHỈ TỒN TẠI tại compile time. Tại runtime, JVM KHÔNG BIẾT về generic types!
Tại sao có Type Erasure?
// Lý do: Backward Compatibility với code Java cũ (trước Java 5)
Java Generics được thiết kế để:
- ✅ Compile-time type safety - kiểm tra type khi compile
- ✅ Backward compatibility - code cũ vẫn chạy được
- ❌ Runtime overhead - không có thông tin type tại runtime
Ví dụ Type Erasure
// Code bạn viết
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// Sau type erasure (code thực tế trong bytecode)
public class Box {
private Object value; // T → Object
public void set(Object value) { // T → Object
this.value = value;
}
public Object get() { // T → Object
return value;
}
}
Type Erasure với Bounded Types
// Code bạn viết
public class NumberBox<T extends Number> {
private T value;
public T getValue() {
return value;
}
public double getDoubleValue() {
return value.doubleValue();
}
}
// Sau type erasure
public class NumberBox {
private Number value; // T extends Number → Number
public Number getValue() {
return value;
}
public double getDoubleValue() {
return value.doubleValue();
}
}
- Unbounded type
<T>→ thay bằngObject - Bounded type
<T extends Number>→ thay bằngNumber(first bound) - Multiple bounds
<T extends Number & Comparable<T>>→ thay bằngNumber(class bound)
Compiler Type Checking và Casting
Compiler tự động chèn type casts khi cần:
// Code bạn viết
Box<String> box = new Box<>();
box.set("Hello");
String value = box.get(); // Không cần cast!
// Bytecode thực tế (sau type erasure)
Box box = new Box();
box.set("Hello");
String value = (String) box.get(); // Compiler tự động chèn cast!
Ví dụ chi tiết
public class TypeErasureDemo {
public static void main(String[] args) {
// Source code
List<String> strings = new ArrayList<>();
strings.add("Java");
String first = strings.get(0);
// Equivalent bytecode (sau type erasure):
List strings = new ArrayList();
strings.add("Java");
String first = (String) strings.get(0); // Cast tự động
}
}
Bridge Methods
Bridge Method Generation Flow
Bridge methods là synthetic methods được compiler tự động tạo ra để maintain type safety sau type erasure.
Ví dụ Bridge Method
// Interface generic
interface Comparable<T> {
int compareTo(T other);
}
// Implementation
class MyInteger implements Comparable<MyInteger> {
private int value;
public MyInteger(int value) {
this.value = value;
}
// Method bạn viết
@Override
public int compareTo(MyInteger other) {
return Integer.compare(this.value, other.value);
}
}
// Sau type erasure, interface thành:
interface Comparable {
int compareTo(Object other);
}
// Compiler tạo bridge method:
class MyInteger implements Comparable {
private int value;
// Bridge method (synthetic)
public int compareTo(Object other) {
return compareTo((MyInteger) other); // Cast và delegate
}
// Your actual method
public int compareTo(MyInteger other) {
return Integer.compare(this.value, other.value);
}
}
Bridge methods giúp maintain type safety và polymorphism sau khi type erasure. Bạn không cần tự tạo - compiler làm tự động!
Xem Bridge Methods
import java.lang.reflect.Method;
public class BridgeMethodExample {
static class Node<T> {
private T data;
public void setData(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
static class IntNode extends Node<Integer> {
@Override
public void setData(Integer data) {
super.setData(data);
}
@Override
public Integer getData() {
return super.getData();
}
}
public static void main(String[] args) {
// Check for bridge methods
for (Method method : IntNode.class.getDeclaredMethods()) {
System.out.println(method.getName() + " - Bridge: " +
method.isBridge());
}
// Output:
// setData - Bridge: false (your method)
// setData - Bridge: true (compiler-generated)
// getData - Bridge: false (your method)
// getData - Bridge: true (compiler-generated)
}
}
Runtime Behavior After Erasure
Tại runtime, JVM chỉ thấy raw types — không có thông tin generic!
public class RuntimeBehavior {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
// Runtime: CẢ HAI đều là ArrayList!
System.out.println(strings.getClass() == integers.getClass());
// Output: true
System.out.println(strings.getClass());
// Output: class java.util.ArrayList (không có <String>!)
// ✅ instanceof with raw type - OK
if (strings instanceof ArrayList) {
System.out.println("It's an ArrayList");
}
// ✅ instanceof with unbounded wildcard - OK
if (strings instanceof List<?>) {
System.out.println("It's a List");
}
// ❌ instanceof with parameterized type - ERROR
// if (strings instanceof ArrayList<String>) { } // Compile error!
// if (strings instanceof List<String>) { } // Compile error!
}
}
ClassCastException After Erasure
Unchecked cast warnings là nguy cơ thật sự — có thể gây ClassCastException!
public class UncheckedCastDanger {
// ⚠️ Unchecked cast - dangerous!
@SuppressWarnings("unchecked")
public static <T> List<T> castList(Object obj) {
return (List<T>) obj; // Compiler warning!
}
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
// Unchecked cast
List<String> strings = castList(integers);
// No exception yet! Type erasure: both are just List
// 💥 ClassCastException when accessing!
String first = strings.get(0);
// Exception: java.lang.Integer cannot be cast to java.lang.String
}
}
Ví dụ thực tế:
public class RealWorldExample {
// Legacy code returns raw List
@Deprecated
public static List getLegacyData() {
List list = new ArrayList();
list.add(Integer.valueOf(42));
list.add(Integer.valueOf(100));
return list;
}
// Modern code assumes String
public static void processStrings() {
@SuppressWarnings("unchecked")
List<String> strings = (List<String>) getLegacyData();
// Unchecked cast - no error yet!
// 💥 Crash khi dùng
for (String s : strings) {
System.out.println(s.toUpperCase());
}
// Exception: java.lang.Integer cannot be cast to java.lang.String
}
}
Unchecked cast không fail ngay lập tức!
List<Integer> ints = Arrays.asList(1, 2, 3);
@SuppressWarnings("unchecked")
List<String> strings = (List<String>) (List<?>) ints;
// Compiles and runs! No exception!
System.out.println(strings); // [1, 2, 3] - looks OK!
// 💥 Exception only when accessing as String
String s = strings.get(0); // CRASH HERE!
Limitations do Type Erasure
Generic Limitations Summary
Type erasure dẫn đến nhiều giới hạn khi sử dụng generics.
1. Không thể tạo instance: new T()
public class CannotInstantiate<T> {
private T instance;
public CannotInstantiate() {
// ❌ Compile error: Type parameter 'T' cannot be instantiated
// instance = new T();
// Runtime không biết T là gì (do type erasure)!
// T có constructor không? Constructor nào? Không biết!
}
}
Workaround 1: Truyền Class object
public class Factory<T> {
private Class<T> type;
public Factory(Class<T> type) {
this.type = type;
}
public T createInstance() throws Exception {
return type.getDeclaredConstructor().newInstance();
}
}
// Sử dụng
Factory<String> factory = new Factory<>(String.class);
String str = factory.createInstance();
Workaround 2: Supplier pattern
public class Container<T> {
private Supplier<T> supplier;
public Container(Supplier<T> supplier) {
this.supplier = supplier;
}
public T createNew() {
return supplier.get();
}
}
// Sử dụng
Container<StringBuilder> container =
new Container<>(StringBuilder::new);
StringBuilder sb = container.createNew();
2. Không thể dùng Primitive Types
// ❌ Compile error - primitive types không được phép
// List<int> numbers = new ArrayList<>();
// Map<long, double> data = new HashMap<>();
// ✅ Phải dùng wrapper classes
List<Integer> numbers = new ArrayList<>();
Map<Long, Double> data = new HashMap<>();
Type erasure biến List<T> thành List, chỉ chứa Objects. Primitives không phải Objects!
Workaround: Specialized collections (thư viện external)
// Eclipse Collections, Trove, FastUtil cung cấp primitive collections
// IntList, LongSet, DoubleList, etc.
3. Không thể instanceof với Generic Types
public class InstanceOfProblem<T> {
public void check(Object obj) {
// ❌ Compile error: illegal generic type for instanceof
// if (obj instanceof T) { }
// ❌ Compile error
// if (obj instanceof List<String>) { }
// ✅ OK - raw type (nhưng warning)
if (obj instanceof List) {
System.out.println("It's a list");
}
// ✅ OK - unbounded
if (obj instanceof List<?>) {
System.out.println("It's a list");
}
}
}
Workaround: Truyền Class object
public class TypeChecker<T> {
private Class<T> type;
public TypeChecker(Class<T> type) {
this.type = type;
}
public boolean isInstance(Object obj) {
return type.isInstance(obj);
}
public T cast(Object obj) {
if (isInstance(obj)) {
return type.cast(obj);
}
throw new ClassCastException();
}
}
// Sử dụng
TypeChecker<String> checker = new TypeChecker<>(String.class);
System.out.println(checker.isInstance("Hello")); // true
System.out.println(checker.isInstance(123)); // false
4. Generic Array Creation Forbidden
Tại sao new List<String>[10] bị cấm? Array covariance + Type erasure = Type safety hole!
// Giả sử cho phép tạo generic array
List<String>[] stringLists = new List<String>[10]; // Giả sử OK
// Array covariance: String[] là subtype của Object[]
Object[] objects = stringLists; // ✅ Array covariance
// Bây giờ có thể add bất kỳ List nào!
objects[0] = new ArrayList<Integer>(); // Compiler cho qua vì type erasure!
// Runtime: ArrayList<Integer> vào List<String>[]
// 💥 Heap pollution!
String s = stringLists[0].get(0); // ClassCastException!
Proof bằng ví dụ:
public class WhyGenericArraysForbidden {
public static void main(String[] args) {
// Nếu compiler cho phép:
// List<String>[] stringLists = new List<String>[1];
// Workaround (unchecked):
@SuppressWarnings("unchecked")
List<String>[] stringLists = (List<String>[]) new List[1];
stringLists[0] = Arrays.asList("Hello");
// Array covariance
Object[] objects = stringLists;
// Type erasure allows this!
objects[0] = Arrays.asList(42); // List<Integer> vào List<String>[]
// Runtime: No ArrayStoreException vì type erasure!
// 💥 Crash khi access
String s = stringLists[0].get(0);
// ClassCastException: Integer cannot be cast to String
}
}
Array covariance + Type erasure = Disaster!
- Arrays are covariant:
String[]is-aObject[] - Arrays check type at runtime: ArrayStoreException
- Generics use type erasure: No runtime type info
- Result: Cannot guarantee type safety → FORBIDDEN
Workaround 1: Dùng List thay vì array
public class GenericContainer<T> {
// ✅ Dùng List thay vì array
private List<T> items = new ArrayList<>();
public void add(T item) {
items.add(item);
}
public T get(int index) {
return items.get(index);
}
}
Workaround 2: Dùng Array.newInstance
public class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(Class<T> type, int size) {
// Tạo array thông qua reflection
array = (T[]) Array.newInstance(type, size);
}
public void set(int index, T value) {
array[index] = value;
}
public T get(int index) {
return array[index];
}
public int length() {
return array.length;
}
}
// Sử dụng
GenericArray<String> stringArray = new GenericArray<>(String.class, 10);
stringArray.set(0, "Hello");
String value = stringArray.get(0);
Workaround 3: Varargs workaround
public class VarargsWorkaround {
@SafeVarargs
public static <T> List<T> asList(T... elements) {
return Arrays.asList(elements);
}
// Sử dụng
List<String> list = asList("A", "B", "C");
}
5. Không thể Overload chỉ khác Generic Type
public class OverloadProblem {
// ❌ Compile error: same erasure
// public void process(List<String> list) { }
// public void process(List<Integer> list) { }
// Cả hai methods có cùng erasure: process(List)
// ✅ OK - tên method khác
public void processStrings(List<String> list) { }
public void processIntegers(List<Integer> list) { }
// ✅ OK - số parameter khác
public void process(List<String> list, String extra) { }
public void process(List<Integer> list, int extra) { }
}
6. Không thể tạo Static Generic Field
public class StaticFieldProblem<T> {
// ❌ Compile error: cannot make static reference to non-static type T
// private static T instance;
// ✅ OK - static method với own type parameter
public static <E> void process(E element) {
// OK
}
// ✅ OK - raw type (nhưng mất type safety)
private static Object instance;
}
Generics in Exceptions
Generics với exceptions có những giới hạn đặc biệt:
// ❌ KHÔNG thể catch parameterized exception
try {
// ...
} catch (SomeException<String> e) { // ❌ Compile error!
// Cannot use parameterized type in catch
}
// Tại sao? Runtime không biết type → cannot catch by parameterized type
// ❌ KHÔNG thể extends Throwable với type parameter
class MyException<T> extends Exception { // ❌ Error!
// Generic class may not extend java.lang.Throwable
}
// ✅ OK: throws với type parameter CÓ bound
public <T extends Exception> void method() throws T {
// OK - nhưng ít practical
}
// ✅ OK: Dùng type parameter trong exception message
public <T> void process(T value) throws IOException {
if (value == null) {
throw new IOException("Null value: " + value.getClass());
}
}
Ví dụ thực tế:
// Pattern: Rethrow exceptions với generic method
public class ExceptionHandler {
// Generic rethrow
public static <T extends Throwable> void rethrow(Throwable t) throws T {
throw (T) t; // Unchecked cast
}
public static void main(String[] args) {
try {
riskyOperation();
} catch (Exception e) {
ExceptionHandler.<RuntimeException>rethrow(e); // Rethrow as unchecked
}
}
static void riskyOperation() throws IOException {
throw new IOException("Failed");
}
}
Reifiable vs Non-Reifiable Types
Reifiable vs Non-Reifiable Diagram
| Category | Types | Runtime Info | Examples |
|---|---|---|---|
| Reifiable | Có thông tin type đầy đủ tại runtime | ✅ Có | Primitives, non-generic classes, raw types, arrays of reifiable types, unbounded wildcards <?> |
| Non-Reifiable | Không có thông tin type đầy đủ | ❌ Không | Generic types: List<String>, Map<K,V>, bounded wildcards <? extends T> |
Reifiable Types
// ✅ Reifiable - có đầy đủ thông tin tại runtime
int x = 5;
String str = "Hello";
Object obj = new Object();
int[] array = new int[10];
List rawList = new ArrayList(); // Raw type
// Có thể dùng instanceof
if (obj instanceof String) { }
if (array instanceof int[]) { }
Non-Reifiable Types
// ❌ Non-reifiable - không có thông tin type tại runtime
List<String> strings = new ArrayList<>();
Map<Integer, String> map = new HashMap<>();
// Runtime chỉ biết là List và Map, không biết type parameters!
// Không thể: if (strings instanceof List<String>)
Heap Pollution Detailed
Heap pollution xảy ra khi một variable của parameterized type trỏ đến object không phải kiểu đó.
Varargs <T> creates Object[], not T[]!
Varargs với generic type là nguồn chính gây heap pollution:
public class VarargsHeapPollution {
// Varargs method với generic type
public static <T> void addAll(List<T> list, T... elements) {
// Warning: Possible heap pollution from parameterized vararg type
// Behind the scenes: elements là Object[], không phải T[]!
for (T element : elements) {
list.add(element);
}
}
// Dangerous example
@SafeVarargs // ❌ LYING! This is NOT safe!
public static <T> T[] dangerous(T... elements) {
return elements; // Returns Object[], not T[]!
}
public static void main(String[] args) {
// Varargs creates Object[]
String[] arr = dangerous("A", "B", "C");
// 💥 ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
// Why? elements is Object[], not String[]!
}
}
Khi @SafeVarargs thật sự safe:
// ✅ SAFE: Only reads, doesn't leak array
@SafeVarargs
public static <T> List<T> asList(T... elements) {
List<T> list = new ArrayList<>();
for (T elem : elements) {
list.add(elem); // Chỉ đọc elements
}
return list; // Không return array
}
// ❌ UNSAFE: Returns varargs array
@SafeVarargs // Should NOT use this!
public static <T> T[] toArray(T... elements) {
return elements; // Leak array → caller gets Object[]!
}
// ❌ UNSAFE: Stores varargs array
static class Container<T> {
private T[] array;
@SafeVarargs // Should NOT use this!
Container(T... elements) {
this.array = elements; // Stores array → unsafe!
}
}
Heap Pollution Examples
public class HeapPollutionExample {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
addToList(strings);
// Heap pollution! List<String> chứa Integer
// Runtime: ClassCastException!
String first = strings.get(0); // 💥
}
// Method gây heap pollution
public static void addToList(List list) { // Raw type!
list.add(123); // Thêm Integer vào List<String>
}
}
Heap pollution với casts:
public class CastHeapPollution {
@SuppressWarnings("unchecked")
public static List<String> getList() {
List raw = new ArrayList();
raw.add(Integer.valueOf(42));
return (List<String>) raw; // Unchecked cast → heap pollution!
}
public static void main(String[] args) {
List<String> strings = getList(); // No exception yet
System.out.println(strings); // [42] - prints OK
// 💥 ClassCastException when using
String s = strings.get(0); // Integer cannot be cast to String
}
}
- Không dùng raw types - Dùng
List<String>thay vìList - Cẩn thận với unchecked casts - Chỉ cast khi chắc chắn
- @SafeVarargs chỉ khi thật sự safe - Không leak/modify/return varargs array
- Prefer List over arrays -
List<T>thay vìT[]
Reflection can recover Type Info
Mặc dù type erasure xóa runtime type info, Reflection vẫn có thể lấy một số thông tin!
import java.lang.reflect.*;
import java.util.*;
public class ReflectionGenerics {
// Field với generic type
private List<String> stringList;
private Map<String, Integer> stringIntMap;
// Method với generic return type
public List<String> getStringList() {
return stringList;
}
public <T extends Number> T processNumber(T number) {
return number;
}
public static void main(String[] args) throws Exception {
// 1. Get generic field type
Field field = ReflectionGenerics.class.getDeclaredField("stringList");
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) genericType;
System.out.println("Raw type: " + pt.getRawType());
// Output: interface java.util.List
System.out.println("Type arguments:");
for (Type typeArg : pt.getActualTypeArguments()) {
System.out.println(" " + typeArg);
}
// Output: class java.lang.String
}
// 2. Get generic method return type
Method method = ReflectionGenerics.class.getMethod("getStringList");
Type returnType = method.getGenericReturnType();
System.out.println("Return type: " + returnType);
// Output: java.util.List<java.lang.String>
// 3. Get method type parameters
Method generic = ReflectionGenerics.class.getMethod(
"processNumber", Number.class
);
TypeVariable<?>[] typeParams = generic.getTypeParameters();
for (TypeVariable<?> tv : typeParams) {
System.out.println("Type parameter: " + tv.getName());
System.out.println("Bounds:");
for (Type bound : tv.getBounds()) {
System.out.println(" " + bound);
}
}
// Output:
// Type parameter: T
// Bounds:
// class java.lang.Number
}
}
Type information available via Reflection:
public class GenericTypeInfo {
static class StringList extends ArrayList<String> { }
public static void main(String[] args) {
// Direct instantiation - NO type info
List<String> list1 = new ArrayList<>();
Type type1 = list1.getClass().getGenericSuperclass();
System.out.println(type1); // class java.util.AbstractList (no <String>)
// Subclass - HAS type info!
StringList list2 = new StringList();
Type type2 = list2.getClass().getGenericSuperclass();
System.out.println(type2); // java.util.ArrayList<java.lang.String>
if (type2 instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type2;
Type[] typeArgs = pt.getActualTypeArguments();
System.out.println("Type argument: " + typeArgs[0]);
// Output: class java.lang.String
}
}
}
Pattern: Type-safe heterogeneous container (Effective Java Item 33)
public class TypeSafeMap {
private Map<Class<?>, Object> map = new HashMap<>();
public <T> void put(Class<T> type, T instance) {
map.put(type, type.cast(instance)); // Runtime type check!
}
public <T> T get(Class<T> type) {
return type.cast(map.get(type)); // Type-safe cast
}
public static void main(String[] args) {
TypeSafeMap tsm = new TypeSafeMap();
tsm.put(String.class, "Hello");
tsm.put(Integer.class, 42);
String s = tsm.get(String.class); // Type-safe!
Integer i = tsm.get(Integer.class); // Type-safe!
// String s2 = tsm.get(Integer.class); // Compile error!
}
}
Performance Impact
Type erasure và boxing/unboxing có performance overhead:
Boxing/Unboxing Overhead
// ⚠️ Performance issue với primitive wrappers
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
numbers.add(i); // 1 million boxing operations!
}
int sum = 0;
for (int num : numbers) {
sum += num; // 1 million unboxing operations!
}
// Mỗi boxing/unboxing:
// 1. Allocate wrapper object (Integer)
// 2. Store primitive value
// 3. Extract primitive value
// → Slow và tốn memory!
// Alternative: Primitive collections (external libraries)
// - Eclipse Collections: IntArrayList
// - Trove: TIntArrayList
// - FastUtil: IntArrayList
Bridge Method Invocation Cost
// Bridge methods add indirection
class Box<T> {
public T get() { return null; }
}
class IntBox extends Box<Integer> {
@Override
public Integer get() { return 42; }
// Compiler generates bridge:
// public Object get() { return get(); }
}
// Invocation:
// 1. Call bridge: Object get()
// 2. Bridge delegates: Integer get()
// 3. Return with cast
// → Extra method call overhead!
- Avoid excessive boxing/unboxing - Use primitives when possible
- Consider primitive collections for large datasets
- Bridge methods có overhead - Hiểu nhưng không tránh được
- Generic code không chậm hơn nhiều - JIT compiler tối ưu hóa
@SuppressWarnings("unchecked")
Annotation này bỏ qua unchecked warnings - chỉ dùng khi bạn chắc chắn code an toàn!
public class SuppressWarningsExample {
/**
* Unchecked cast - nhưng an toàn trong context này
*/
@SuppressWarnings("unchecked")
public static <T> T[] toArray(List<T> list, Class<T> type) {
T[] array = (T[]) Array.newInstance(type, list.size());
for (int i = 0; i < list.size(); i++) {
array[i] = list.get(i);
}
return array;
}
/**
* Unchecked cast từ raw type
*/
@SuppressWarnings("unchecked")
public static <K, V> Map<K, V> createMap() {
// Legacy code trả về raw Map
Map rawMap = getLegacyMap();
// Cast an toàn vì biết structure
return (Map<K, V>) rawMap;
}
private static Map getLegacyMap() {
return new HashMap();
}
}
- Minimally scoped: Chỉ áp dụng cho smallest scope cần thiết
- Documented: Comment giải thích tại sao suppress
- Actually safe: Chắc chắn code thực sự type-safe
- Last resort: Sau khi đã cố gắng fix warning
OCP Traps Summary: Type Erasure Edition
Trap 1: new T() illegal - no constructor info at runtime
class Factory<T> {
T create() {
return new T(); // ❌ Error! Cannot instantiate T
// Runtime không biết T là class nào!
}
// Fix: Pass Class<T>
T create(Class<T> clazz) throws Exception {
return clazz.getDeclaredConstructor().newInstance(); // ✅ OK
}
}
Trap 2: T[] arr = new T[10] illegal - cannot create generic array
class Container<T> {
T[] array = new T[10]; // ❌ Error!
// Workaround 1: Array.newInstance
@SuppressWarnings("unchecked")
T[] createArray(Class<T> type, int size) {
return (T[]) Array.newInstance(type, size); // ✅ OK
}
// Workaround 2: Use List
List<T> list = new ArrayList<>(); // ✅ Better!
}
Trap 3: static T field illegal - static belongs to class, not instance
class Box<T> {
private static T value; // ❌ Error!
// Static field cannot reference type parameter T
// T chỉ biết khi có instance: Box<String>, Box<Integer>
// Static: Box.value → T là gì? Không biết!
// ✅ OK: Static method với own type parameter
public static <E> Box<E> create(E value) {
return new Box<>(value);
}
}
Trap 4: Runtime type check fails
public <T> void check(Object obj) {
if (obj instanceof T) { } // ❌ Error!
if (obj instanceof List<String>) { } // ❌ Error!
// ✅ OK với Class object
void check(Object obj, Class<T> type) {
if (type.isInstance(obj)) { } // ✅ OK
}
}
Trap 5: Varargs returns Object[], not T[]
@SafeVarargs // ❌ Lying!
static <T> T[] makeArray(T... elements) {
return elements; // Returns Object[], not T[]!
}
String[] arr = makeArray("A", "B"); // 💥 ClassCastException!
Mermaid Diagram: What Survives Erasure
Giải thích chi tiết:
- Source → Compiler → Bytecode → Runtime
- Type Erasure xóa
<T>,<String>, etc. - Casts được chèn tự động bởi compiler
- Bridge methods tạo ra để maintain polymorphism
- Runtime chỉ thấy raw types (Box, List, Object)
- Signatures survive trong bytecode (có thể đọc qua Reflection)
Best Practices
- Hiểu limitations: Biết những gì không thể làm
- Prefer List over Array: List linh hoạt hơn với generics
- Pass Class objects: Khi cần runtime type information
- Use @SafeVarargs: Cho varargs methods an toàn
- Avoid raw types: Luôn dùng parameterized types
- Document casts: Giải thích unchecked casts
Bài tập tổng hợp
Bài 1: Generic Stack với Array
Implement generic Stack sử dụng array internally:
public class GenericStack<T> {
private T[] elements;
private int size;
@SuppressWarnings("unchecked")
public GenericStack(int capacity) {
// TODO: Tạo array với Array.newInstance hoặc cast
// elements = (T[]) new Object[capacity];
}
public void push(T element) {
// TODO: Implement
}
public T pop() {
// TODO: Implement
}
public T peek() {
// TODO: Implement
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
}
Bài 2: Type-Safe Heterogeneous Container
Tạo container có thể chứa nhiều types khác nhau một cách type-safe:
public class TypeSafeContainer {
private Map<Class<?>, Object> container = new HashMap<>();
public <T> void put(Class<T> type, T instance) {
// TODO: Store instance with type
}
public <T> T get(Class<T> type) {
// TODO: Retrieve và cast type-safe
return null;
}
}
// Sử dụng:
// container.put(String.class, "Hello");
// container.put(Integer.class, 42);
// String str = container.get(String.class);
// Integer num = container.get(Integer.class);
Bài 3: Generic Method với Runtime Type
public class RuntimeTypeExample {
/**
* Filter collection theo runtime type
*/
public static <T> List<T> filterByType(
Collection<?> collection,
Class<T> type) {
// TODO: Filter elements where element instanceof type
return null;
}
}
// Sử dụng:
// List<Object> mixed = Arrays.asList("A", 1, "B", 2, "C");
// List<String> strings = filterByType(mixed, String.class);
// List<Integer> numbers = filterByType(mixed, Integer.class);
Bài 4: Safe Generic Array Creation
public class SafeGenericArray<T> {
private final T[] array;
@SafeVarargs
public SafeGenericArray(T... elements) {
// TODO: Safely store elements
this.array = elements;
}
public T get(int index) {
// TODO: Implement
return null;
}
public void set(int index, T value) {
// TODO: Implement
}
public int length() {
return array.length;
}
public T[] toArray() {
return Arrays.copyOf(array, array.length);
}
}
Bài 5: Reflection với Generics
Viết utility method để lấy generic type information:
public class GenericTypeUtils {
/**
* Get generic superclass type parameter
*/
public static Type[] getGenericTypes(Class<?> clazz) {
// TODO: Use reflection để lấy type parameters
// Hint: getGenericSuperclass(), ParameterizedType
return null;
}
}
// Sử dụng với:
// class StringList extends ArrayList<String> {}
// Type[] types = getGenericTypes(StringList.class);
Tóm tắt
| Khái niệm | Ý nghĩa | Hệ quả |
|---|---|---|
| Type Erasure | Xóa generic info tại runtime | Nhiều limitations |
| Bridge Methods | Synthetic methods bởi compiler | Maintain polymorphism |
| Reifiable | Có type info tại runtime | Arrays, primitives |
| Non-Reifiable | Không có type info | Generic types |
| Heap Pollution | Variable trỏ sai kiểu | ClassCastException |
Type erasure là compromise để đạt backward compatibility. Hiểu limitations giúp bạn:
- Viết code generics đúng cách
- Tránh các lỗi runtime
- Sử dụng workarounds hiệu quả
- Thiết kế APIs tốt hơn
Generics mạnh mẽ nhưng cần hiểu đúng để dùng hiệu quả!