Java: Как мне получить буквальный класс из родового типа?

StackOverflow https://stackoverflow.com/questions/2390662

  •  24-09-2019
  •  | 
  •  

Вопрос

Как правило, я видел, как люди используют буквальный класс, как это:

Class<Foo> cls = Foo.class;

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

Class<List> cls = List.class

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

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

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

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

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

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

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

Это было полезно?

Решение

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

Java Generics немного больше, чем синтаксический сахар для литейных объектов. Показывать:

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:

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

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

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

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

Другие советы

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

Смотрите java.lang.reflect.parameterizedType - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/parameterizedtype.html.

Библиотека GOGLE GSON определяет типованный класс, который позволяет просто генерировать параметризованные типы и использует его для объектов SPEC JSON со сложными параметрами. В вашем примере вы бы использовали:

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

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

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

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

Чтобы излагаться на ответ 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();

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

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

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;
    }
}

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

static void test1() {
        CaptureType<String> t1 = new CaptureType<String>() {
        };
        equals(t1.getRawType(), String.class);
    }

    static void test2() {
        CaptureType<List<String>> t1 = new CaptureType<List<String>>() {
        };
        equals(t1.getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), String.class);
    }


    private static void test3() {
            CaptureType<List<List<String>>> t1 = new CaptureType<List<List<String>>>() {
            };
            equals(t1.getParamADT().getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), List.class);
    }

    static class Test4 extends CaptureType<List<String>> {
    }

    static void test4() {
        Test4 test4 = new Test4();
        equals(test4.getParamADT().getRawType(), List.class);
    }

    static class PreTest5<S> extends CaptureType<Integer> {
    }

    static class Test5 extends PreTest5<Integer> {
    }

    static void test5() {
        Test5 test5 = new Test5();
        equals(test5.getTypeParam(), Integer.class);
    }

    static class PreTest6<S> extends CaptureType<S> {
    }

    static class Test6 extends PreTest6<Integer> {
    }

    static void test6() {
        Test6 test6 = new Test6();
        equals(test6.getTypeParam(), Integer.class);
    }



    class X<T> extends CaptureType<T> {
    }

    class Y<A, B> extends X<B> {
    }

    class Z<Q> extends Y<Q, Map<Integer, List<List<List<Integer>>>>> {
    }

    void test7(){
        Z<String> z = new Z<>();
        TypeADT param = z.getParamADT();
        equals(param.getRawType(), Map.class);
        List<TypeADT> 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<int[]> type = new CaptureType<int[]>() {
        };
        equals(type.getRawType(), int[].class);
    }

    static void test9(){
        CaptureType<String[]> type = new CaptureType<String[]>() {
        };
        equals(type.getRawType(), String[].class);
    }

    static class SomeClass<T> extends CaptureType<T>{}
    static void test10(){
        SomeClass<String> 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<T> extends CaptureType<T>{}
SomeClass<String> claz = new SomeClass<>();

где он устремилен.

Из-за разоблаченного факта того, что классовые литералы не имеют информации об общем типе, я думаю, вы должны предположить, что невозможно избавиться от всех предупреждений. В некотором смысле, используя 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);
}

Вы можете использовать метод помощника, чтобы избавиться от @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) {
}

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

То Часто задаваемые вопросы Java И поэтому и клит отвечать звук, как нет смысла иметь 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. Запрос переключиться на использование подстановочных знаков, однако это не похоже, что это изменение будет выполнено (очень скоро), так как он несовместим с предыдущим кодом (см. Этот комментарий).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top