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

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

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中List的非参数化使用所引起的警告,但我宁愿不这样做。

有什么建议吗?

222164 次浏览

你不能因为类型擦除

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对象。类文字can 用于运行时类型检查和 反射。< / p >

参数化类型失去其类型 参数 类中编译时的字节码 进程称为类型擦除。作为一个 都是擦除字体的副作用 泛型类型共享的实例化 同样的运行时表示, 即相应的生料 类型。换句话说,参数化 类型没有类型表示 他们自己的。因此,有 没有必要形成类字面量 例如List<String>.classList<Long>.classList<?>.class ,因为不存在这样的Class对象。 只有原始类型ListClass 对象,该对象表示其运行时 类型。它被称为 List.class . < / p >

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

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

参数化类型没有Class字面量,但是有正确定义这些类型的Type对象。

< p >看到java.lang.reflect.ParameterizedType ——http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html < / p >

谷歌的Gson库定义了一个TypeToken类,允许简单地生成参数化类型,并使用它以通用友好的方式规范具有复杂参数化类型的json对象。在你的例子中,你可以用:

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

我打算发布到TypeToken和Gson类javadoc的链接,但堆栈溢出不会让我发布多个链接,因为我是一个新用户,你可以很容易地使用谷歌搜索找到它们

由于暴露的事实是Class字面量没有泛型类型信息,我认为您应该假定不可能消除所有警告。在某种程度上,使用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);
}

我们都知道它会被抹去。但在类层次结构中显式提到类型的某些情况下可以知道:

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

它被擦掉了。

你可以使用双重强制转换:

< p > <代码> @SuppressWarnings(“unchecked”) Class< List< Foo>比;cls = (Class< List< Foo>祝辞)(对象)List.class < /代码> < / p >

你可以使用helper方法在整个类中去除@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泛型常见问题和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请求切换到使用通配符,但是看起来不会执行这个更改(很快),因为它与前面的代码不兼容(参见这样的评论)。