Java:如何从泛型类型中获取类文字?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2390662/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java: how do I get a class literal from a generic type?
提问by Tom
Typically, I've seen people use the class literal like this:
通常,我见过人们像这样使用类文字:
Class<Foo> cls = Foo.class;
But what if the type is generic, e.g. List? This works fine, but has a warning since List should be parameterized:
但是如果类型是泛型的,例如 List 呢?这工作正常,但有一个警告,因为 List 应该被参数化:
Class<List> cls = List.class
So why not add a <?>
? Well, this causes a type mismatch error:
那么为什么不添加一个<?>
?好吧,这会导致类型不匹配错误:
Class<List<?>> cls = List.class
I figured something like this would work, but this is just a plain ol' syntax error:
我认为这样的事情会起作用,但这只是一个普通的语法错误:
Class<List<Foo>> cls = List<Foo>.class
How can I get a Class<List<Foo>>
statically, e.g. using the class literal?
我怎样才能Class<List<Foo>>
静态地得到一个,例如使用类文字?
I coulduse @SuppressWarnings("unchecked")
to get rid of the warnings caused by the non-parameterized use of List in the first example, Class<List> cls = List.class
, but I'd rather not.
我可以使用@SuppressWarnings("unchecked")
,以摆脱在第一个例子中所造成的非参数使用列表,警告的Class<List> cls = List.class
,但我宁愿不要。
Any suggestions?
有什么建议?
采纳答案by cletus
You can't due to type erasure.
你不能因为类型擦除。
Java generics are little more than syntactic sugar for Object casts. To demonstrate:
Java 泛型只不过是对象强制转换的语法糖。展示:
List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = (List<String>)list1;
list2.add("foo"); // perfectly legal
The only instance where generic type information is retained at runtime is with Field.getGenericType()
if interrogating a class's members via reflection.
在运行时保留泛型类型信息的唯一实例是Field.getGenericType()
if 通过反射询问类的成员。
All of this is why Object.getClass()
has this signature:
这就是为什么Object.getClass()
有这个签名的原因:
public final native Class<?> getClass();
The important part being Class<?>
.
重要的部分是Class<?>
.
To put it another way, from the Java Generics FAQ:
换句话说,来自Java 泛型常见问题解答:
Why is there no class literal for concrete parameterized types?
Because parameterized type has no exact runtime type representation.
A class literal denotes a
Class
object that represents a given type. For instance, the class literalString.class
denotes theClass
object that represents the typeString
and is identical to theClass
object that is returned when methodgetClass
is invoked on aString
object. A class literal can be used for runtime type checks and for reflection.Parameterized types lose their type arguments when they are translated to byte code during compilation in a process called type erasure . As a side effect of type erasure, all instantiations of a generic type share the same runtime representation, namely that of the corresponding raw type . In other words, parameterized types do not have type representation of their own. Consequently, there is no point in forming class literals such as
List<String>.class
,List<Long>.class
andList<?>.class
, since no suchClass
objects exist. Only the raw typeList
has aClass
object that represents its runtime type. It is referred to asList.class
.
为什么没有具体参数化类型的类文字?
因为参数化类型没有精确的运行时类型表示。
类文字表示
Class
表示给定类型的对象。例如,类字面量String.class
表示Class
表示类型的对象,String
并且与在Class
对象上getClass
调用方法时返回的String
对象相同。类文字可用于运行时类型检查和反射。参数化类型在编译期间在称为类型擦除的过程中转换为字节码时会丢失其类型参数。作为类型擦除的副作用,泛型类型的所有实例共享相同的运行时表示,即相应原始类型的表示。换句话说,参数化类型没有自己的类型表示。因此,形成诸如
List<String>.class
,List<Long>.class
和 之类的类文字是没有意义的List<?>.class
,因为不Class
存在这样的对象。只有原始类型List
具有Class
表示其运行时类型的对象。它被称为List.class
。
回答by Jim Garrison
To expound on cletus' answer, at runtime all record of the generic types is removed. Generics are processed only in the compiler and are used to provide additional type safety. They are really just shorthand that allows the compiler to insert typecasts at the appropriate places. For example, previously you'd have to do the following:
为了阐述 cletus 的答案,在运行时删除了泛型类型的所有记录。泛型仅在编译器中处理,用于提供额外的类型安全。它们实际上只是允许编译器在适当的位置插入类型转换的速记。例如,以前您必须执行以下操作:
List x = new ArrayList();
x.add(new SomeClass());
Iterator i = x.iterator();
SomeClass z = (SomeClass) i.next();
becomes
变成
List<SomeClass> x = new ArrayList<SomeClass>();
x.add(new SomeClass());
Iterator<SomeClass> i = x.iterator();
SomeClass z = i.next();
This allows the compiler to check your code at compile-time, but at runtime it still looks like the first example.
这允许编译器在编译时检查您的代码,但在运行时它仍然看起来像第一个示例。
回答by Santi P.
There are no Class literals for parameterized types, however there are Type objects that correctly define these types.
参数化类型没有 Class 字面量,但是存在正确定义这些类型的 Type 对象。
See java.lang.reflect.ParameterizedType - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html
参见 java.lang.reflect.ParameterizedType - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html
Google's Gson library defines a TypeToken class that allows to simply generate parameterized types and uses it to spec json objects with complex parameterized types in a generic friendly way. In your example you would use:
Google 的 Gson 库定义了一个 TypeToken 类,它允许简单地生成参数化类型,并使用它以通用友好的方式指定具有复杂参数化类型的 json 对象。在您的示例中,您将使用:
Type typeOfListOfFoo = new TypeToken<List<Foo>>(){}.getType()
I intended to post links to the TypeToken and Gson classes javadoc but Stack Overflow won't let me post more than one link since I'm a new user, you can easily find them using Google search
我打算发布指向 TypeToken 和 Gson 类 javadoc 的链接,但由于我是新用户,因此 Stack Overflow 不允许我发布多个链接,您可以使用 Google 搜索轻松找到它们
回答by Thiago Chaves
Due to the exposed fact that Class literals doesn't have generic type information, I think you should assume that it will be impossible to get rid of all the warnings. In a way, using Class<Something>
is the same as using a collection without specifying the generic type. The best I could come out with was:
由于暴露的类字面量没有泛型类型信息,我认为您应该假设不可能摆脱所有警告。在某种程度上,使用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);
}
回答by Jatin
Well as we all know that it gets erased. But it can be known under some circumstances where the type is explicitly mentioned in the class hierarchy:
众所周知,它会被删除。但是在类层次结构中明确提到类型的某些情况下,它是可以知道的:
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;
}
}
And now you can do things like:
现在您可以执行以下操作:
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);
}
}
More info here. But again, it is almost impossible to retrieve for:
更多信息在这里。但同样,几乎不可能检索到:
class SomeClass<T> extends CaptureType<T>{}
SomeClass<String> claz = new SomeClass<>();
where it gets erased.
它被擦除的地方。
回答by slaurent
You can manage it with a double cast :
您可以使用双重演员来管理它:
@SuppressWarnings("unchecked")
Class<List<Foo>> cls = (Class<List<Foo>>)(Object)List.class
@SuppressWarnings("unchecked")
Class<List<Foo>> cls = (Class<List<Foo>>)(Object)List.class
回答by aventurin
You could use a helper method to get rid of @SuppressWarnings("unchecked")
all over a class.
您可以使用辅助方法来摆脱@SuppressWarnings("unchecked")
整个类。
@SuppressWarnings("unchecked")
private static <T> Class<T> generify(Class<?> cls) {
return (Class<T>)cls;
}
Then you could write
然后你可以写
Class<List<Foo>> cls = generify(List.class);
Other usage examples are
其他使用示例是
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) {
}
However, since it is rarely really useful, and the usage of the method defeats the compiler's type checking, I would not recommend to implement it in a place where it is publicly accessible.
但是,由于它很少真正有用,并且该方法的使用会破坏编译器的类型检查,因此我不建议在可公开访问的地方实现它。
回答by Marcono1234
The Java Generics FAQand therefore also cletus' answersound like there is no point in having Class<List<T>>
, however the real problem is that this is extremely dangerous:
在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);
The cast()
makes it look as if it is safe, but in reality it is not safe at all.
这cast()
使它看起来好像是安全的,但实际上它根本不安全。
Though I don't get where there can't be List<?>.class
= Class<List<?>>
since this would be pretty helpful when you have a method which determines the type based on the generic type of a Class
argument.
虽然我不明白不能存在List<?>.class
= 的地方,Class<List<?>>
因为当您有一种方法可以根据Class
参数的泛型类型确定类型时,这将非常有用。
For getClass()
there is JDK-6184881requesting to switch to using wildcards, however it does not look like this change will be performed (very soon) since it is not compatible with previous code (see this comment).
因为getClass()
有JDK-6184881请求切换到使用通配符,但是看起来不会执行此更改(很快),因为它与以前的代码不兼容(请参阅此注释)。