2015-03-05 2 views
4

У Java есть стирание типа, и люди говорят, что нельзя определить тип универсального объекта во время выполнения без хаков. Рассмотрим приведенный ниже кодJava generics, тип стирания и тип общего элемента

public class TestClass<T> { 

    private T genericField; 

    public TestClass(T genericField) { 
     this.genericField = genericField; 
    } 

    public void printTypeInfo() { 
     System.out.println("Hi I'm a " + genericField.getClass()); 
     System.out.println("Am I a string? " + (genericField instanceof String)); 
     System.out.println("Am I a long? " + (genericField instanceof Long)); 
    } 

    public static void main(String [] args) { 
     TestClass<String> genericString = new TestClass<>("Hello"); 
     TestClass<Long> genericLong = new TestClass<>(111111L); 
     genericString.printTypeInfo(); 
     System.out.println("------------------"); 
     genericLong.printTypeInfo(); 
    } 
} 

Это дает мне следующий результат:

Hi I'm a class java.lang.String 
Am I a string? true 
Am I a long? false 
------------------ 
Hi I'm a class java.lang.Long 
Am I a string? false 
Am I a long? true 

Кажется информации типа легко доступны во время выполнения. Что мне здесь не хватает?

ответ

4

Вы можете определить тип любого объекта в genericField во время выполнения, но вы не можете определить разницу между TestClass<X> и TestClass<Y> во время выполнения без рассмотрения некоторых членов, которые вы знаете, случается, сдерживаются общего типа. То есть вы не можете определить параметр типа TestClass<...> с учетом экземпляра TestClass.

Ваш код отображает тип значения genericField «s, не параметризованный тип экземпляра TestClass. Попробуйте распечатать this.getClass(), и вы увидите, что он идентичен в обоих случаях.

Что вы «пропустили»: это, по-видимому, неправильное соединение между фактом, что genericField сам содержит объект (с типом) и тот факт, что TestClass имеет параметр типового типа. Вы сбиваете с толку возможность определить тип значения genericField s с возможностью определения параметра типа, указанного в TestClass. То есть, в то время как вы можете определить, какой параметр типа был основан на , ваш знал, что genericField - T, это не то же самое, что напрямую определить, что T было, что невозможно.

Другой способ смотреть на предыдущем пункте, чтобы рассмотреть эти вопросы:

  • Если TestClass не было членов типа T то нет никакого другого пути вы могли бы извлечь T. Ваш код только «определяет», что T был основан на ваших личных знаниях, что genericField был объявлен как один и тот же тип (и, следовательно, объект в нем должен быть такого типа, и поэтому вы можете заключить, что общий параметр, вероятно, тип или некоторый супертип).

  • Если вы не использовали генерики, и genericField был просто Object, вы все еще быть в состоянии определить тип объекта в genericField. То есть его тип является «независимым» от общего типа, за исключением случаев, когда вы используете generics, компилятор помещает ограничение на тип. Это все еще просто произвольный объект после компиляции, независимо от того, используете ли вы дженерики (которые на самом деле просто удобны, поскольку вы могли бы сделать все это без дженериков и просто использовать Object и множество отливок).

Рассмотрим также возможность TestClass<Base>, где genericField отводилась Derived. Ваш код правильно показал бы, что genericField был Derived, но вы не знаете, что параметр типа был Base по сравнению с Derived, поскольку информация была удалена.


Кроме того, таранить выше точки дома еще дальше:

TestClass<String> genericString = new TestClass<String>("Hello"); 
TestClass<?> kludge = genericString; 
TestClass<Long> genericLongButNotReally = (TestClass<Long>)kludge; 

genericLongButNotReally.printTypeInfo(); 

Выдает Информация для String (вот почему эти предупреждения «бесконтрольно преобразования» даны, чтобы предотвратить странные вещи, как это), не заботясь о том, что genericLongButNotReally был указан с параметром типа Long. Клад необходим, чтобы обойти хорошую защиту, которую предлагает компилятор, когда вы используете общие типы; но во время выполнения все равно.

4
TestClass<Number> genericNumber = new TestClass<>(42L); 
genericNumber.printTypeInfo(); 

Это будет печатать Hi I'm a Long вместо Hi I'm a Number. Вы можете видеть, что genericField является Long, но вы не можете видеть, что T был создан как Number.

Вот пример того, что вы не можете сделать из-за стирания стилей.

TestClass<?> generic = new TestClass<String>("Hello"); 

if (generic instanceof TestClass<String>) { 
    System.out.println("It holds a string!"); 
} 
else if (generic instanceof TestClass<Long>) { 
    System.out.println("It holds a long!"); 
} 
+0

Очень ясно; также стоит отметить, что печать 'this.getClass()' в 'printTypeInfo()' даст одинаковые результаты для обоих тестовых примеров, что поможет выделить разницу между способностью определять тип значения «genericField» и способностью для определения параметра типа экземпляра 'TestClass'. –

1

Возможность получить тип переменной и тип объекта - это две разные вещи.

Только потому, что вы можете получить тип genericField, это не значит, что вы можете видеть, что T было числом.

Смежные вопросы