Java:我如何从一个genericstypes获得类文字?

通常,我已经看到人们使用这样的类文字:

Class<Foo> cls = Foo.class; 

但是,如果types是通用的,例如List? 这工作正常,但有一个警告,因为列表应该参数化:

 Class<List> cls = List.class 

那么为什么不添加一个<?> ? 那么,这会导致types不匹配错误:

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

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

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

我怎样才能得到一个Class<List<Foo>>静态,例如使用类文字?

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

有什么build议么?

你不能由于types的删除 。

Javagenerics只不过是Object Object的语法糖。 展示:

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

通用types信息在运行时保留的唯一实例是Field.getGenericType()如果通过reflection来询问类的成员。

所有这些都是为什么Object.getClass()具有这个签名:

 public final native Class<?> getClass(); 

重要的部分是Class<?>

换句话说,从Java Generics FAQ :

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

由于参数化types没有确切的运行时types表示。

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

参数化types在被称为types擦除的过程中在编译期间被转换为字节码时会丢失其types参数。 作为types擦除的副作用,genericstypes的所有实例化共享相同的运行时表示,即相应的原始types。 换句话说,参数化types没有自己的types表示。 因此,由于不存在这样的Class对象,所以在形成诸如List<String>.classList<Long>.classList<?>.class类文字方面没有任何意义。 只有原始typesList有一个表示其运行时types的Class对象。 它被称为List.class

没有用于参数化types的类文字,但有正确定义这些types的Type对象。

请参阅java.lang.reflect.ParameterizedType – http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html

Google的Gson库定义了一个TypeToken类,它允许简单地生成参数化types,并以通用友好的方式使用它来指定具有复杂参数化types的JSON对象。 在你的例子中,你会使用:

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

我打算发布链接到TypeToken和Gson类javadoc,但Stack Overflow不会让我发布多个链接,因为我是一个新用户,可以使用Googlesearch轻松find它们

你可以通过双击来pipe理它:

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

为了阐明cletus的答案,在运行时所有通用types的logging被删除。 generics只在编译器中进行处理,用于提供附加的types安全性。 它们实际上只是简写,允许编译器在适当的位置插入types转换。 例如,以前您必须执行以下操作:

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

这允许编译器在编译时检查你的代码,但在运行时它仍然看起来像第一个例子。

由于暴露的事实,即类文字没有genericstypes的信息,我认为你应该假设它是不可能摆脱所有的警告。 在某种程度上,使用Class<Something>与使用集合而不指定genericstypes相同。 最好的我可以出来是:

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

那么我们都知道它被抹去了。 但是在某些情况下可以知道在类层次结构中明确提到了types:

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

它被擦除的地方。

你可以使用一个辅助方法来摆脱@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) { } 

但是,由于这种方法很less有用,而且这种方法的使用会影响编译器的types检查,所以我不build议在可公开访问的地方实现它。