Ява: як мені отримати буквальний клас із загального типу?


193

Зазвичай я бачив, як люди використовують класний буквал так:

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, але я вважаю за краще це не робити.

Будь-які пропозиції?

Відповіді:


160

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

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.


12
List<Integer> list1 = new ArrayList<Integer>(); List<String> list2 = (List<String>)list1; list2.add("foo"); // perfectly legal Ви не можете цього робити на Java, ви отримуєте помилку компіляції невідповідності!
DhafirNz

4
так ... що мені робити, якщо він мені потрібен?
Крістофер Франсіско

2
Ви завжди можете обдурити компілятор доList<String> list2 = (List<String>) (Object) list1;
kftse

17
Ще одне для мене "Це просто працює в C #, але не на Java". Я десериалізую об’єкт JSON, а typeof (Список <MyClass>) прекрасно працює в C #, але Список <MyClass> .class - це синтаксична помилка в Java. Так, є логічне пояснення цього, як звичайно, як писав Клет, але мені завжди цікаво, чому всі ці речі просто працюють у C #.
Чортові овочі

2
що ти означає це абсолютно законно? Ця частина коду не компілюється?
Едуардо Денніс

63

Для параметризованих типів немає літералів 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()

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


1
Завдяки цьому я зміг створити клас із загальним E, а потім використати, clzz = new TypeToken<E>(){}.getRawType();щоб пізніше перебирати аналогічно структуровані перерахунки з, clzz.getEnumConstants()а потім, нарешті, використовувати референцію для виклику методів-членів a la Method method = clzz.getDeclaredMethod("getSomeFoo");much win! Спасибі!
Наруто Семпай

57

Ви можете керувати ним подвійним складом:

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


2
Змінивши другий кастинг Objectна, Classви, ймовірно, можете зберегти накладні витрати (безглуздого) перевіреного режиму виконання.
Clashsoft

2
@Clashsoft Використання Classзамість Object, як ви пропонуєте, здається більш значущим, але це не знімає потреби в @SuppressWarnings("unchecked")анотації, воно навіть додає нове попередження:Class is a raw type. References to generic type Class<T> should be parameterized
Ortomala Lokni

10
Ви можете скористатись Class<?>:(Class<List<Foo>>)(Class<?>)List.class
Devstr

@Devstr Я бачу, що ви правильні, коли я намагаюся це зробити ... Які аргументи для використання або (Object), або (Class <?>)?
cellepo

2
Ця відповідь абсолютно безглузда. Причиною, що OP хотів параметризувати шлях до класу, було те, що він отримав uncheckedпопередження. Ця відповідь нічого не змінює / не покращує. ОП навіть у своєму запитанні заявляє, що він не хоче використовувати SuppressWarnings...
Spenhouet

6

Щоб пояснити відповідь клетуса, під час виконання всі записи родових типів видаляються. Генеричні файли обробляються лише у компіляторі та використовуються для забезпечення додаткової безпеки типу. Вони насправді є лише скороченням, що дозволяє компілятору вставляти набірні типи у відповідні місця. Наприклад, раніше вам доведеться зробити наступне:

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
На мою думку, це просто означає, що загальне було реалізовано посередньо Sun, сподіваємось, Oracle це виправить це одного дня. Впровадження C # у загальне набагато набагато краще (Андерс - богоподібний)
Marcel Valdez Orozco

1
@MarcelValdezOrozco AFAIK, на Java вони реалізували його таким чином, оскільки хотіли, щоб старий код (до-1,5) працював над новими JVM без жодних проблем. Здається, це дуже розумне дизайнерське рішення, яке дбає про сумісність. Я не думаю, що в цьому є щось посереднє.
peter.petrov

3

Java Дженерики FAQ і тому також Клетус відповідь звучить як немає сенсу в тому 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 з проханням перейти на використання символів узагальнення, проте це не виглядає , як ця зміна буде виконано (дуже скоро) , так як він не сумісний з попереднім кодом (див цей коментар ).


2

Ну а всі ми знаємо, що це стирається. Але це може бути відомо за деяких обставин, коли тип явно згадується в ієрархії класів:

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<>();

де воно стирається.


Це також рішення, яке використовує JAX-RS, пор. GenericEntityі GenericType.
Хайн Блод

1

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

1

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.