Java: как получить литерал класса из универсального типа? - PullRequest
169 голосов
/ 06 марта 2010

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

Class<Foo> cls = Foo.class;

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

Class<List> cls = List.class

Так почему бы не добавить <?>? Ну, это вызывает ошибку несоответствия типов:

Class<List<?>> cls = List.class

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

Class<List<Foo>> cls = List<Foo>.class

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

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

Есть предложения?

Ответы [ 8 ]

146 голосов
/ 06 марта 2010

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

Обобщения Java - немного больше, чем синтаксический сахар для приведения объектов. Для демонстрации:

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

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

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

public final native Class<?> getClass();

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

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

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

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

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

Параметризованные типы теряют свой тип аргументы, когда они переводятся на Байт-код во время компиляции в Процесс называется стирание типа. Как побочный эффект типа стирания, все создание экземпляров общего типа то же представление во время выполнения, а именно, что из соответствующего сырья тип . Другими словами, параметризованный типы не имеют представления типа их собственного. Следовательно, есть нет смысла в формировании литералов класса такие как List<String>.class, List<Long>.class и List<?>.class , поскольку таких Class объектов не существует. Только необработанный тип List имеет Class объект, который представляет его время выполнения тип. Это называется List.class.

53 голосов
/ 08 марта 2010

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

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

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

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

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

46 голосов
/ 10 июня 2015

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

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

6 голосов
/ 06 марта 2010

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

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

становится

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

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

2 голосов
/ 27 апреля 2015

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

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<T> {
    /**
     * {@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<TypeVariable<?>, Type> reifyMap = new LinkedHashMap<>();

        for (; ; ) {
            Type genericSuper = bottom.getGenericSuperclass();
            if (!(genericSuper instanceof Class)) {
                ParameterizedType generic = (ParameterizedType) genericSuper;
                Class<?> actualClaz = (Class<?>) generic.getRawType();
                TypeVariable<? extends Class<?>>[] 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.
     * <p>For example in case of {@code CaptureType<String>}, it would return {@code Class<String>}</p>
     * 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<T> 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.
     * <p>If the type is an {@link java.lang.reflect.ParameterizedType}, then it returns its {@link java.lang.reflect.ParameterizedType#getRawType()}</p>
     *
     * @param type The type
     * @param <A>  type of class object expected
     * @return The Class<A> 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 <A> Class<A> getClass(Type type) {
        if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            Class<?> componentClass = getClass(componentType);
            if (componentClass != null) {
                return (Class<A>) 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.
     * <p>It returns {@link com.types.TypeADT} object which contains nested information of the type parameters</p>
     *
     * @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<TypeADT> 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<TypeADT> parametrized;

    TypeADT(Class<?> reify, List<TypeADT> parametrized) {
        this.reify = reify;
        this.parametrized = parametrized;
    }

    public Class<?> getRawType() {
        return reify;
    }

    public List<TypeADT> getParameters() {
        return parametrized;
    }
}

И теперь вы можете делать такие вещи:здесь .Но, опять же, почти невозможно найти:

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

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

1 голос
/ 08 марта 2010

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

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

public <C extends A<C>> List<A<C>> getList() {
    return getList(A.class);
}
0 голосов
/ 04 января 2019

Часто задаваемые вопросы по Java Generics и, следовательно, также answer cletus звучат так, как будто нет смысла иметь Class<List<T>>, однако реальная проблема заключается в том, что это чрезвычайно опасно: *

@SuppressWarnings("unchecked")
Class<List<String>> stringListClass = (Class<List<String>>) (Class<?>) List.class;

List<Integer> intList = new ArrayList<>();
intList.add(1);
List<String> stringList = stringListClass.cast(intList);
// Surprise!
String firstElement = stringList.get(0);

* * * * * * * * * * * * * * * * * * * cast() выглядит так, как будто это безопасно, но на самом деле это вовсе не безопасно.


Хотя я не понимаю, где не может быть List<?>.class = Class<List<?>>, поскольку это было бы весьма полезно, если у вас есть метод, который определяет тип на основе универсального типа аргумента Class.

Для getClass() существует JDK-6184881 , запрашивающий переключение на использование подстановочных знаков, однако не похоже, что это изменение будет выполнено (очень скоро), поскольку оно несовместимо с предыдущим кодом (см. этот комментарий ).

0 голосов
/ 10 ноября 2016

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

@SuppressWarnings("unchecked")
private static <T> Class<T> generify(Class<?> cls) {
    return (Class<T>)cls;
}

Тогда вы можете написать

Class<List<Foo>> cls = generify(List.class);

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

  Class<Map<String, Integer>> cls;

  cls = generify(Map.class);

  cls = TheClass.<Map<String, Integer>>generify(Map.class);

  funWithTypeParam(generify(Map.class));

public void funWithTypeParam(Class<Map<String, Integer>> cls) {
}

Однако, поскольку он редко действительно полезен, а использование метода не позволяет проверять тип компилятора, я бы не рекомендовал реализовывать его там, где он общедоступен.

...