Почему не все данные типа стираются в Java во время выполнения?

Мое, очевидно, неправильное понимание Java Generics было до сих пор, что Type Erasure удаляет всю информацию о типе, так что во время выполнения ничего не осталось. Недавно я наткнулся на фрагмент кода, где я должен был спросить себя: как этот хак делает это? Упрощенный, он представлен как:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public abstract class SuperClass<T> {

    private final Type type;

    protected SuperClass(){
        ParameterizedType parameterizedType =
                (ParameterizedType) getClass().getGenericSuperclass();
        type = parameterizedType.getActualTypeArguments()[0];
    }

    public void tellMyType(){
        System.out.println("Hi, my type parameter is " + type);
    }    
}

и

public class Example {

    public static void main(String[] args) {
        SuperClass sc = new SuperClass<Integer>(){};
        sc.tellMyType();
    }
}

Выполнение основного класса приводит к Hi, my type parameter is class java.lang.Integer.

Здесь мы видим, что информация о типе T также доступна во время выполнения, что противоречит моему первоначальному пониманию.

Итак, мой вопрос: почему компилятор сохраняет это? Требуется ли это для некоторого внутреннего поведения JVM или есть ли разумное объяснение этого эффекта?

Ответ 1

Из http://www.artima.com/weblogs/viewpost.jsp?thread=208860:

Оказывается, что, хотя JVM будет не отслеживать фактические аргументы типа для экземпляров родового класса это отслеживает фактические аргументы типа для подклассов общих классов. В другими словами, в то время как новый ArrayList<String>() действительно просто new ArrayList() во время выполнения, если класс продолжается ArrayList<String>, тогда JVM знает, что String является фактическим аргумент типа для типа ListПараметр.

В вашем случае вы создаете анонимный подкласс параметризованного типа, поэтому информация о типе сохраняется. См. Статью для подробного объяснения.

Ответ 2

Параметры типа стираются только из динамических типов (т.е. типа создаваемого объекта):

Object o = new ArrayList<String>(); // String erased

Он сохраняется в статических типах (т.е. поле, аргумент и возвращаемые типы, предложение throws, объявления суперкласса и суперинтерфейса):

class Test implements Superclass<String> { // String retained 
    // Accessible via Class.getGenericSuperclass()

    private List<Integer> l; // Integer retained (via Field.getGenericType())

    public void test(List<Long> l) {} // Long retained (via Method.getGenericParameterTypes())

    // Character retained (via Method.getGenericReturnType())
    public List<Character> test() { return null; }
}

В вашем случае вы создаете анонимный подкласс SuperClass<Integer>, поэтому параметр type сохраняется в объявлении суперкласса.

Ответ 3

Google Guice использует это для создания TypeLiteral представлять общие классы во время выполнения. Например

TypeLiteral<List<String>> list = new TypeLiteral<List<String>>() {};

но

Class<List<String>> list = List<String>.class;

не будет компилироваться.

Этот метод известен как "токен супер-типа" (см. статья Нила Гафтера по этому вопросу).

Ответ 5

Я не мог заставить ParameterizedType работать для меня (я не мог изменить код SuperClass). Однако следующий простой код работал нормально:

try
    {
    SuperClass<Integer> castSc = (SuperClass<Integer>)sc;
    // Do what you want to do if generic type is Integer...
    }
catch (ClassCastException ignored)
    {
    }