Почему использование 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. Ни стирание, ни ограничения не требуют, чтобы это было неверно.