Java:如何从泛型类型获取类文字?


193

通常,我见过人们像这样使用类文字:

Class<Foo> cls = Foo.class;

但是,如果类型是通用类型,例如List,该怎么办?这可以正常工作,但由于应将List参数化,因此发出警告:

Class<List> cls = List.class

那么为什么不添加一个<?>呢?好吧,这会导致类型不匹配错误:

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

我想像这样的事情会起作用,但这只是一个普通的语法错误:

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

我如何获得Class<List<Foo>>静态信息,例如使用类文字?

可以使用@SuppressWarnings("unchecked"),以摆脱在第一个例子中所造成的非参数使用列表,警告的Class<List> cls = List.class,但我宁愿不要。

有什么建议?

Answers:


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泛型常见问题解答

为什么没有具体的参数化类型的类文字?

因为参数化类型没有确切的运行时类型表示形式。

类文字表示Class 表示给定类型的对象。例如,类文字 String.class表示Class 表示类型的对象, String并且与在 Class对象上getClass调用方法时返回的 String对象相同。类文字可用于运行时类型检查和反射。

当在编译过程中将参数化类型转换为字节码时,它们会丢失其类型参数,该过程称为类型擦除。作为类型擦除的副作用,泛型类型的所有实例共享相同的运行时表示,即相应原始类型的实例。换句话说,参数化类型没有自己的类型表示。因此,存在在形成文字类诸如没有意义的List<String>.classList<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 2015年

4
所以...如果我需要一个怎么办?
Christopher Francisco

2
您可以随时通过List<String> list2 = (List<String>) (Object) list1;
kftse '16

17
对我来说,还有另一个“它只适用于C#,而不适用于Java”。我正在反序列化JSON对象,并且typeof(List <MyClass>)在C#中可以正常工作,但是List <MyClass> .class在Java中是语法错误。是的,就像Cletus所写的那样,对此有一个合乎逻辑的解释,但是我总是想知道为什么所有这些东西都只能在C#中工作。
该死的蔬菜

2
您什么意思完全合法?那部分代码无法编译?
Eduardo Dennis

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的链接,但是Stack Overflow不允许我发布多个链接,因为我是新用户,您可以使用Google搜索轻松找到它们


1
有了这个,我就可以创建一个具有通用E的类,然后用于clzz = new TypeToken<E>(){}.getRawType();稍后对具有类似结构的枚举进行迭代clzz.getEnumConstants(),然后最终使用refection来调用成员方法,这是一个Method method = clzz.getDeclaredMethod("getSomeFoo");很大的胜利!谢谢!
火影忍者神巴

57

您可以通过双重转换来管理它:

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


2
通过将第二个强制转换从更改ObjectClass,可以节省(无意义的)已检查运行时强制转换的开销。
Clashsoft '16

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警告。这个答案不会改变/改善任何一个。OP甚至在他的问题中声明他不想使用SuppressWarnings...
Spenhouet

6

为了阐明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();

这使编译器可以在编译时检查您的代码,但是在运行时,它仍然看起来像第一个示例。


感谢您的附加说明-我对泛型的理解现在变得更加清晰,以至于我意识到它们不是运行时机制。:)
汤姆(Tom)2010年

2
在我看来,这仅意味着通用是由Sun以中等的方式实现的,希望Oracle有一天会对此进行修复。C#的泛型实现要好得多(Anders就像上帝一样)
Marcel Valdez Orozco 2013年

1
@MarcelValdezOrozco AFAIK,他们用Java实现了这种方式,因为他们希望旧代码(1.5之前的版本)可以在新的JVM上工作而没有任何问题。似乎这是一个非常明智的设计决定,它关心兼容性。我认为这没有什么平庸的。
peter.petrov 2015年

3

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请求切换为使用通配符,但是由于它与先前的代码不兼容,因此看起来(很快)将不会执行此更改(请参阅此注释)。


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使用的解决方法,请参阅。GenericEntityGenericType
HeinBlöd16年

1

由于公开的事实是Class文字没有通用类型信息,我认为您应该假定不可能摆脱所有警告。在某种程度上,using 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) {
}

但是,由于它很少真正有用,并且该方法的使用使编译器的类型检查失败,所以我不建议在可公开访问的地方实现它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.