Почему не все данные типа стираются в 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;
не будет компилироваться.
Этот метод известен как "токен супер-типа" (см. статья Нила Гафтера по этому вопросу).
Ответ 4
Этот FAQ по стиранию типа может помочь.
Ответ 5
Я не мог заставить ParameterizedType работать для меня (я не мог изменить код SuperClass). Однако следующий простой код работал нормально:
try
{
SuperClass<Integer> castSc = (SuperClass<Integer>)sc;
// Do what you want to do if generic type is Integer...
}
catch (ClassCastException ignored)
{
}