Java: как мне получить литерал classа из общего типа?

Как правило, я видел, как люди используют литерал classа следующим образом:

Class cls = Foo.class; 

Но что, если тип является общим, например List? Это отлично работает, но имеет предупреждение, поскольку параметр List должен быть параметризован:

 Class cls = List.class 

Так почему бы не добавить ? Это вызывает ошибку несоответствия типа:

 Class<List> cls = List.class 

Я решил, что что-то вроде этого будет работать, но это всего лишь простая синтаксическая ошибка:

 Class<List> cls = List.class 

Как я могу получить Class<List> статически, например, используя литерал classа?

Я мог бы использовать @SuppressWarnings("unchecked") чтобы избавиться от предупреждений, вызванных @SuppressWarnings("unchecked") использованием List в первом примере, Class cls = List.class , но я бы предпочел.

Какие-либо предложения?

Вы не можете из-за стирания типа .

Дженерики Java – это нечто большее, чем синтаксический сахар для объектов. Демонстрировать:

 List list1 = new ArrayList(); List list2 = (List)list1; list2.add("foo"); // perfectly legal 

Единственный экземпляр, где информация о родовом типе сохраняется во время выполнения, Field.getGenericType() с Field.getGenericType() при опросе членов classа посредством отражения.

Все это потому, что Object.getClass() имеет эту подпись:

 public final native Class getClass(); 

Важной частью является Class .

Другими словами, из часто задаваемых вопросов Java Generics :

Почему нет литерала classа для конкретных параметризованных типов?

Поскольку параметризованный тип не имеет точного представления типа времени выполнения.

Литерал classа обозначает объект Class который представляет заданный тип. Например, class literal String.class обозначает объект Class который представляет тип String и идентичен объекту Class который возвращается, когда метод getClass вызывается в объекте String . Литерал classа может использоваться для проверки типа времени выполнения и для отражения.

Параметрированные типы теряют свои аргументы типа, когда они переводятся в байтовый код во время компиляции в процессе, называемом стиранием типа. В качестве побочного эффекта стирания типа все экземпляры родового типа имеют одно и то же представление времени исполнения, а именно соответствующего типа raw. Другими словами, параметризованные типы не имеют собственного представления типа. Следовательно, нет смысла создавать classные литералы, такие как List.class , List.class и List.class , поскольку таких объектов Class существует. Только исходный тип List имеет объект Class который представляет его тип выполнения. Он называется List.class .

Нет литералов classа для параметризованных типов, однако существуют объекты типа, которые правильно определяют эти типы.

См. Java.lang.reflect.ParameterizedType – http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html

Библиотека Gson от Google определяет class TypeToken, который позволяет просто генерировать параметризованные типы и использует его для спецификации json-объектов со сложными параметризованными типами в общем дружественном виде. В вашем примере вы будете использовать:

 Type typeOfListOfFoo = new TypeToken>(){}.getType() 

Я собирался размещать ссылки на classы TypeToken и Gson javadoc, но Stack Overflow не позволяет мне отправлять более одной ссылки, так как я новый пользователь, вы можете легко найти их с помощью поиска Google

Вы можете управлять им с помощью двойного нажатия:

@SuppressWarnings("unchecked") Class> cls = (Class>)(Object)List.class

Чтобы изложить ответ cletus, во время выполнения удаляется вся запись общих типов. Генерики обрабатываются только в компиляторе и используются для обеспечения дополнительной безопасности типов. Они на самом деле просто сокращены, что позволяет компилятору вставлять типы меток в соответствующие места. Например, ранее вам нужно было сделать следующее:

 List x = new ArrayList(); x.add(new SomeClass()); Iterator i = x.iterator(); SomeClass z = (SomeClass) i.next(); 

становится

 List x = new ArrayList(); x.add(new SomeClass()); Iterator i = x.iterator(); SomeClass z = i.next(); 

Это позволяет компилятору проверять ваш код во время компиляции, но во время выполнения он по-прежнему выглядит как первый пример.

Ну, как мы все знаем, что он стирается. Но это может быть известно при некоторых обстоятельствах, когда тип явно упоминается в иерархии classов:

 import java.lang.reflect.*; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; public abstract class CaptureType { /** * {@link java.lang.reflect.Type} object of the corresponding generic type. This method is useful to obtain every kind of information (including annotations) of the generic type. * * @return Type object. null if type could not be obtained (This happens in case of generic type whose information cant be obtained using Reflection). Please refer documentation of {@link com.types.CaptureType} */ public Type getTypeParam() { Class bottom = getClass(); Map, Type> reifyMap = new LinkedHashMap<>(); for (; ; ) { Type genericSuper = bottom.getGenericSuperclass(); if (!(genericSuper instanceof Class)) { ParameterizedType generic = (ParameterizedType) genericSuper; Class actualClaz = (Class) generic.getRawType(); TypeVariable>[] typeParameters = actualClaz.getTypeParameters(); Type[] reified = generic.getActualTypeArguments(); assert (typeParameters.length != 0); for (int i = 0; i < typeParameters.length; i++) { reifyMap.put(typeParameters[i], reified[i]); } } if (bottom.getSuperclass().equals(CaptureType.class)) { bottom = bottom.getSuperclass(); break; } bottom = bottom.getSuperclass(); } TypeVariable var = bottom.getTypeParameters()[0]; while (true) { Type type = reifyMap.get(var); if (type instanceof TypeVariable) { var = (TypeVariable) type; } else { return type; } } } /** * Returns the raw type of the generic type. * 

For example in case of {@code CaptureType}, it would return {@code Class}

* For more comprehensive examples, go through javadocs of {@link com.types.CaptureType} * * @return Class object * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType} * @see com.types.CaptureType */ public Class
getRawType() { Type typeParam = getTypeParam(); if (typeParam != null) return getClass(typeParam); else throw new RuntimeException("Could not obtain type information"); } /** * Gets the {@link java.lang.Class} object of the argument type. *

If the type is an {@link java.lang.reflect.ParameterizedType}, then it returns its {@link java.lang.reflect.ParameterizedType#getRawType()}

* * @param type The type * @param type of class object expected * @return The Class object of the type * @throws java.lang.RuntimeException If the type is a {@link java.lang.reflect.TypeVariable}. In such cases, it is impossible to obtain the Class object */ public static Class getClass(Type type) { if (type instanceof GenericArrayType) { Type componentType = ((GenericArrayType) type).getGenericComponentType(); Class componentClass = getClass(componentType); if (componentClass != null) { return (Class) Array.newInstance(componentClass, 0).getClass(); } else throw new UnsupportedOperationException("Unknown class: " + type.getClass()); } else if (type instanceof Class) { Class claz = (Class) type; return claz; } else if (type instanceof ParameterizedType) { return getClass(((ParameterizedType) type).getRawType()); } else if (type instanceof TypeVariable) { throw new RuntimeException("The type signature is erased. The type class cant be known by using reflection"); } else throw new UnsupportedOperationException("Unknown class: " + type.getClass()); } /** * This method is the preferred method of usage in case of complex generic types. *

It returns {@link com.types.TypeADT} object which contains nested information of the type parameters

* * @return TypeADT object * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType} */ public TypeADT getParamADT() { return recursiveADT(getTypeParam()); } private TypeADT recursiveADT(Type type) { if (type instanceof Class) { return new TypeADT((Class) type, null); } else if (type instanceof ParameterizedType) { ArrayList generic = new ArrayList<>(); ParameterizedType type1 = (ParameterizedType) type; return new TypeADT((Class) type1.getRawType(), Arrays.stream(type1.getActualTypeArguments()).map(x -> recursiveADT(x)).collect(Collectors.toList())); } else throw new UnsupportedOperationException(); } } public class TypeADT { private final Class reify; private final List parametrized; TypeADT(Class reify, List parametrized) { this.reify = reify; this.parametrized = parametrized; } public Class getRawType() { return reify; } public List getParameters() { return parametrized; } }

И теперь вы можете делать такие вещи, как:

 static void test1() { CaptureType t1 = new CaptureType() { }; equals(t1.getRawType(), String.class); } static void test2() { CaptureType> t1 = new CaptureType>() { }; equals(t1.getRawType(), List.class); equals(t1.getParamADT().getParameters().get(0).getRawType(), String.class); } private static void test3() { CaptureType>> t1 = new CaptureType>>() { }; equals(t1.getParamADT().getRawType(), List.class); equals(t1.getParamADT().getParameters().get(0).getRawType(), List.class); } static class Test4 extends CaptureType> { } static void test4() { Test4 test4 = new Test4(); equals(test4.getParamADT().getRawType(), List.class); } static class PreTest5 extends CaptureType { } static class Test5 extends PreTest5 { } static void test5() { Test5 test5 = new Test5(); equals(test5.getTypeParam(), Integer.class); } static class PreTest6 extends CaptureType { } static class Test6 extends PreTest6 { } static void test6() { Test6 test6 = new Test6(); equals(test6.getTypeParam(), Integer.class); } class X extends CaptureType { } class Y extends X { } class Z extends Y>>>> { } void test7(){ Z z = new Z<>(); TypeADT param = z.getParamADT(); equals(param.getRawType(), Map.class); List parameters = param.getParameters(); equals(parameters.get(0).getRawType(), Integer.class); equals(parameters.get(1).getRawType(), List.class); equals(parameters.get(1).getParameters().get(0).getRawType(), List.class); equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getRawType(), List.class); equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getParameters().get(0).getRawType(), Integer.class); } static void test8() throws IllegalAccessException, InstantiationException { CaptureType type = new CaptureType() { }; equals(type.getRawType(), int[].class); } static void test9(){ CaptureType type = new CaptureType() { }; equals(type.getRawType(), String[].class); } static class SomeClass extends CaptureType{} static void test10(){ SomeClass claz = new SomeClass<>(); try{ claz.getRawType(); throw new RuntimeException("Shouldnt come here"); }catch (RuntimeException ex){ } } static void equals(Object a, Object b) { if (!a.equals(b)) { throw new RuntimeException("Test failed. " + a + " != " + b); } } 

Больше информации здесь . Но опять же, почти невозможно получить:

 class SomeClass extends CaptureType{} SomeClass claz = new SomeClass<>(); 

где он стирается.

Из-за того факта, что литералы classа не имеют информации о типовом типе, я думаю, вы должны предположить, что избавиться от всех предупреждений будет невозможно. В некотором смысле использование Class совпадает с использованием коллекции без указания типа generic. Лучшее, что я мог вынести, было:

 private > List getList(Class cls) { List res = new ArrayList(); // "snip"... some stuff happening in here, using cls return res; } public > List> getList() { return getList(A.class); } 

Вы можете использовать вспомогательный метод, чтобы избавиться от @SuppressWarnings("unchecked") всему classу.

 @SuppressWarnings("unchecked") private static  Class generify(Class cls) { return (Class)cls; } 

Тогда вы могли бы написать

 Class> cls = generify(List.class); 

Другие примеры использования

  Class> cls; cls = generify(Map.class); cls = TheClass.>generify(Map.class); funWithTypeParam(generify(Map.class)); public void funWithTypeParam(Class> cls) { } 

Тем не менее, поскольку он редко очень полезен, и использование метода нарушает проверку типа компилятора, я бы не рекомендовал его реализовать в том месте, где он доступен для публики.

  • почему sizeof ('a') равен 4 в C?
  • C ++ многоканальный литерал
  • Неожиданное сохранение данных
  • Как указать байтовый литерал в Java?
  • Строковые литералы const?
  • Зачем писать 1000 000 000 как 1000 * 1000 * 1000 в C?
  • Устаревшее преобразование из строкового литерала в 'char *'
  • Interesting Posts

    Скрыть расширение / свернуть символ или деактивировать спецификацию. строки в jqGrid subgrid

    MySQL: Предоставить ** все ** привилегии в базе данных

    Поддерживает ли EL перегруженные методы?

    cpp – valgrind – Недопустимое чтение размером 8

    Обнуление / регистрация собственного трафика Bluetooth на Android

    Windows и Ubuntu с двойной загрузкой. Нет меню для выбора ОС?

    Изменив жесткий диск на ssd, могу ли я перенести содержимое, чтобы я мог переключать диски без хлопот?

    Каковы допустимые символы в субдомене?

    Использование «Файл» для сохранения мест объектов объекта для последующего восстановления в AS3

    Один монитор работает точно так же, как двойной монитор?

    Как установить программу по умолчанию для открытия файлов без расширения в Windows?

    Django: группа по дате (день, месяц, год)

    Установите Windows 7 с USB-накопителя

    Перенаправление HTTP на HTTPS в App Engine

    Windows chkdsk заменил плохой кластер – файлы теперь повреждены?

    Давайте будем гением компьютера.