Type erasure là gì?

Chắc bạn nào làm java cũng biết đến khái niệm "type erasure" trong lập trình generic. Thông tin chi tiết có ghi trên homepage.

Về cơ chế: khi lập trình generic tức là bạn phải truyền "type" vào một method hoặc class nào đó, ở java sử dụng ký hiệu <T>, ở scala sử dụng kí hiệu [T]. Vì lý do lịch sử, khi truyền một type T vào đâu đó java compiler sẽ "xoá" toàn bộ type parameter được truyền vào và thay toàn bộ type T bằng type Object. Bạn có thể hiểu quá trình type erasure khá dễ dàng thông qua ví dụ sau:

Ví dụ 1: Class

  • before
public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) }
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}
  • after
public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}

Ví dụ 2: Method

  • before
public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}
  • after
public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

Type Erasure gây ra vấn đề gì

Nói một cách đơn giản, nó gây ra vấn đề khi bạn muốn "thao tác" với type bạn truyền vào.
Trên trang chủ của oracle chúng ta có một list những hạn chế với generic :
https://docs.oracle.com/javase/tutorial/java/generics/restrictions.html

Mình ví dụ một hạn chế đơn giản nhất là: không thể khởi tạo mới một type generic được truyền vào

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

Vụ ở trên là một vụ rất hay gặp khi lập trình với java. Để work around nó thì chúng ta có thể truyền Class Object vào

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

Type Erasure và scala

Khi lập trình generic với scala, bạn sẽ gặp những vấn đề hoàn toàn tương tự, vì generic trong scala cũng bị erase tương tự như java.
Thử dùng lại ví dụ tương tự ở trên của java:

def append[T](list:List[T]) = {
    val c = new T() //compile error
    list.append(c)
}

Để work around thì chúng ta cũng làm một thao tác y hệt là truyền class vào, cơ mà trong scala thì thay vào truyền Class Object thì chúng ta truyền một thứ gọi là ClassManifest

def append[T](list:List[T], m: ClassManifest[T]) = {
    val c =  m.erasure.newInstance() //OK
    list.append(c)
}

Tuy nhiên lần nào cũng truyền ClassManifest vào thì khá thừa thãi đặc biệt là khi T chỉ đơn thuần là các primitive type như Int hay là Float.., martin xem ra không thích việc này và ông đã implement sẵn cho chúng ta ClassManifest của hầu hết các object thông dụng. Do đó chúng ta chỉ cần thêm từ khoá implicit

def append[T](list:List[T], implicit m: ClassManifest[T]) = {
    val c =  m.erasure.newInstance() //OK
    list.append(c)
}

append(List(1,2,3)) //OK