Почему использование Collection<String>.class незаконно?
Я озадачен дженериками. Вы можете объявить поле как:
Class<Collection<String>> clazz = ...
Кажется логичным, что вы можете назначить это поле с помощью:
Class<Collection<String>> clazz = Collection<String>.class;
Тем не менее, это приводит к ошибке:
Синтаксическая ошибка на токене ">", после этого токена ожидается пустота
Так выглядит .class
оператор не работает с дженериками. Итак, я попробовал:
class A<S> { }
class B extends A<String> { }
Class<A<String>> c = B.class;
Также не работает, генерирует:
Несоответствие типов: невозможно преобразовать из
Class<Test.StringCollection> to Class<Collection<String>>
Теперь я действительно не понимаю, почему это не должно работать. Я знаю, что универсальные типы не реализованы, но в обоих случаях они полностью безопасны, не имея доступа к универсальным типам во время выполнения. У кого-нибудь есть идея?
3 ответа
Обобщения являются инвариантами.
Object o = "someString"; // FINE!
Class<Object> klazz = String.class; // DOESN'T COMPILE!
// cannot convert from Class<String> to Class<Object>
В зависимости от того, что вам нужно, вы можете использовать подстановочные знаки.
Class<? extends Number> klazz = Integer.class; // FINE!
Или, возможно, вам нужно что-то вроде этого:
Class<List<String>> klazz =
(Class<List<String>>) new ArrayList<String>().getClass();
// WARNING! Type safety: Unchecked cast from
// Class<capture#1-of ? extends ArrayList> to Class<List<String>>
Что касается нереализованного во время выполнения примера, вы, кажется, хорошо разбираетесь, но в любом случае вот цитата из Учебников Java по универсальным шаблонам: "Прекрасный шрифт: общий класс является общим для всех его вызовов":
Что печатает следующий фрагмент кода?
List <String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getClass());
Вы могли бы испытать желание сказать
false
, но ты ошибаешься. Это печатаетtrue
потому что все экземпляры универсального класса имеют один и тот же класс времени выполнения, независимо от их фактических параметров типа.
То есть такого понятия, как List<String>.class
или же List<Integer>.class
; есть только List.class
,
Это также отражено в литералах класса JLS 15.8.2.
Литерал класса - это выражение, состоящее из имени класса, интерфейса, массива или типа примитива или псевдотипа void, за которым следует
.
и знакclass
,
Обратите внимание на отсутствие каких-либо допущений для параметров / аргументов универсального типа. Более того,
Это ошибка времени компиляции, если происходит любое из следующего:
- Именованный тип является переменной типа или параметризованным типом, или массивом, тип элемента которого является переменной типа или параметризованным типом.
То есть, это также не компилируется:
void <T> test() {
Class<?> klazz = T.class; // DOESN'T COMPILE!
// Illegal class literal for the type parameter T
}
По сути, вы не можете использовать дженерики с литералами классов, потому что это просто не имеет смысла: они не реализованы.
Я согласен с другими ответами и хотел бы объяснить еще один момент:
Объекты класса представляют классы, которые загружаются в память JVM. Каждый объект класса на самом деле является экземпляром в памяти .class
файл. Обобщения Java не являются отдельными классами. Они являются лишь частью механизма проверки типов во время компиляции. Следовательно, они не имеют представления времени выполнения в объекте класса.
Кажется, в Java не хватает литералов классов, нет способа создать литералы классов с общей информацией, хотя в некоторых случаях это может быть полезно. Поэтому следующий код не может быть вызван, потому что невозможно предоставить литерал класса
class A<S> {}
<S> A<S> foo( Class<A<S>> clazz ) {}
A<String> a = foo( A<String>.class ) // error
Тем не менее, моя главная проблема заключалась в том, что я не мог назвать его с классом B, расширяющим A. Это было вызвано ограничениями инвариантности. Это было решено с помощью подстановочного знака:
class A<S> {}
class B extends A<String> {}
<S> A<S> foo( Class<? extends A<S>> clazz ) { return null; }
void test () {
A<String> s = foo( B.class );
}
Тем не менее, я не нашел причину, что основная причина в том, что Class<A<S>>.class
является недействительным. Кажется, что ни стирание, ни границы не требуют, чтобы это было недействительным.