Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Java

Type Erasure (Xóa kiểu) trong Java Generics

Type Erasure là quá trình compiler Java xóa thông tin kiểu generic (type parameters) tại compile-time, thay thế bằng Object hoặc bound type. Bytecode runtime không chứa generic type info.

Tại sao cần Type Erasure?

  • Backward compatibility: Java 5 generics phải chạy trên JVM pre-generics (Java 1.4) không sửa đổi bytecode format.
  • Migration compatibility: Code non-generic cũ tương tác seamless với code generic mới.

Quy trình Type Erasure (3 bước)

  1. Replace type parameters: Unbounded <T> → Object. Bounded <T extends Number> → Number (leftmost bound).
  2. Insert synthetic casts: Thêm ép kiểu (checkcast) tại các chỗ cần type safety (get, assignment).
  3. Generate bridge methods: Khi override method generic trong subclass/interface, compiler tạo bridge method để bảo toàn polymorphism.

Ví dụ minh họa

// Source code
List list = new ArrayList<>();
list.add("Hello");
String s = list.get(0); // Không cần cast

// Sau Type Erasure (bytecode tương đương)
List list = new ArrayList();           // Raw type
list.add("Hello");                     // Unchecked warning tại compile
String s = (String) list.get(0);       // Synthetic cast: checkcast java/lang/String

Ví dụ Bridge Method

// Source
interface Comparable { int compareTo(T o); }
class MyClass implements Comparable {
    public int compareTo(MyClass other) { return 0; }
}

// Bytecode: Compiler sinh thêm bridge method
// public int compareTo(Object o) { return compareTo((MyClass)o); } // Bridge
// public int compareTo(MyClass other) { return 0; } // Actual

Hệ quả quan trọng (Gotchas)

Reified Generics (Không có trong Java, có trong Kotlin/C#)

Reified generics: type parameter available tại runtime. Java dùng Type Token pattern (super type token) để workaround:

// Super Type Token (Gson, Jackson dùng)
TypeToken> token = new TypeToken>() {}; // Anonymous subclass giữ type info
Type type = token.getType(); // ParameterizedType: List

// Hoặc dùng Class cho simple case
public  T parse(String json, Class clazz) { ... }
parse(json, MyClass.class);

Ví dụ từ nguồn gốc (Raw type + Iterator)

import java.util.*;

public class TypeErasureDemo {
    public static void main(String[] args) {
        List myList = new ArrayList(); // Raw type (pre-generics style)
        myList.add("Hi there");
        
        // Iterator raw type
        for (Iterator iter = myList.iterator(); iter.hasNext(); ) {
            String myStr = (String) iter.next(); // Explicit cast cần thiết
            System.out.println(myStr);
        }
        
        // Generic style (compile-time safety)
        List genericList = new ArrayList<>();
        genericList.add("Hi");
        for (String s : genericList) { // Implicit cast an toàn
            System.out.println(s);
        }
    }
}

Kết luận

  • Type Erasure là trade-off để backward compatibility.
  • Generic type info chỉ tồn tại tại compile-time (source code, class file signature attribute).
  • Runtime: chỉ raw types + synthetic casts + bridge methods.
  • Hiểu erasure giúp tránh heap pollution, thiết kế API tốt hơn, debug ClassCastException lạ.
  • Project Valhalla (tương lai) có thể mang reified generics / specialized generics.
Hệ quảMô tảVí dụ
Không thể new T()T bị erasure thành Object → không biết constructor.Dùng Supplier hoặc reflection.
Không thể T[] arrayArray cần component type runtime. Generic array creation không allowed.Dùng List hoặc (T[]) new Object[n] (unchecked).
Không thể instanceof TT erased → instanceof raw type.Dùng Class token + clazz.isInstance(obj).
Không thể overload bằng genericErasure trùng nhau.void m(List) và void m(List) → conflict.
Static field/method không dùng type parameterStatic thuộc class raw, không phải instance.static T field; lỗi compile.
Exception generic không được catchCatch clause cần runtime type.catch (T e) lỗi. Dùng catch (Exception e) + check.
Heap pollutionRaw type gán vào generic var → runtime ClassCastException ở chỗ khác.Dùng @SafeVarargs, tránh raw type, varargs generic cẩn thận.