Почему использование Collection <String>.class запрещено?
Я озадачен дженериками. Вы можете объявить поле, например:
Class<Collection<String>> clazz = ...
Кажется логичным, что вы можете присвоить этому полю:
Class<Collection<String>> clazz = Collection<String>.class;
Однако это порождает ошибку:
Синтаксическая ошибка в токене " > ", после этого токена ожидается void
Итак, похоже, что оператор .class
не работает с дженериками. Поэтому я попробовал:
class A<S> { }
class B extends A<String> { }
Class<A<String>> c = B.class;
Также не работает, генерирует:
Несоответствие типов: невозможно преобразовать из Class<Test.StringCollection> to Class<Collection<String>>
Теперь я действительно не понимаю, почему это не должно работать. Я знаю, что общие типы не подтверждены, но в обоих случаях он, кажется, полностью безопасен по типу, не имея доступа к родовым типам времени выполнения. Кто-нибудь есть идея?
Ответы
Ответ 1
Дженерики являются инвариантными.
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 Tutorials on Generics, Тонкая печать: общий класс разделяется всеми его вызовами:
Что выводит следующий фрагмент кода?
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 Class Literals
Литерал класса - это выражение, состоящее из имени класса, интерфейса, массива или примитивного типа или псевдотипа типа, а затем .
и токена class
.
Обратите внимание на отсутствие какого-либо разрешения для параметров/аргументов типового типа. Кроме того,
Это ошибка времени компиляции, если происходит одно из следующих событий:
- Именованный тип - это переменная типа или параметризованный тип, или массив, тип элемента которого является переменной типа или параметризованным типом.
То есть это также не компилируется:
void <T> test() {
Class<?> klazz = T.class; // DOESN'T COMPILE!
// Illegal class literal for the type parameter T
}
В принципе вы не можете использовать generics с литералами класса, потому что это просто не имеет смысла: они не подтверждены.
Ответ 2
Я согласен с другими ответами и хотел бы еще один пояснить:
Объекты класса представляют классы, загружаемые в память JVM. Каждый объект класса фактически является экземпляром in-memory файла .class
. Java-дженерики не являются отдельными классами. Они всего лишь часть механизма проверки типа компиляции. Поэтому они не имеют представления во время выполнения в объекте класса.
Ответ 3
Кажется, что нет недостатка в литературе классов в 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
. Ни стирание, ни ограничения не требуют, чтобы это было неверно.