如何安全地投射通用通配符“?”Java中的已知类型参数?
如何安全地投射Class<?>(由 返回Class.forName())Class<Annotation>而不发出“未经检查的投射”警告?
private static Class<? extends Annotation> getAnnotation() throws ClassNotFoundException {
final Class<?> loadedClass = Class.forName("java.lang.annotation.Retention");
if (!Annotation.class.isAssignableFrom(loadedClass)) {
throw new IllegalStateException("@Retention is expected to be an annotation.");
}
@SuppressWarnings("unchecked")
final Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) loadedClass;
return annotationClass;
}
回答
在深入研究答案之前,需要解释多种误解。
您使用了错误的方差
final Class<Annotation> annotationClass = (Class<Annotation>) loadedClass;
在任何情况下,这实际上都是非法的。尝试一下:
Class<Number> n = Integer.class;
那不会编译。
泛型是不变的。这意味着在 中<>,您不能使用超类型作为子类型的替身,反之亦然。
普通 java(<>不涉及时)是covariant。任何子类型都是其超类型之一的替代品。这个:
Number n = Integer.valueOf(5);
是完全合法的java。但在泛型世界中并非如此。如果你想要它,那么,你必须选择它:X extends Y你如何选择协变,以及X super Y你如何选择逆变(逆变就好像Integer i = new Number();是合法的 - 一个超级类型可以代表一个子类型)。
这一切都是因为这就是宇宙最终运行的方式。如果泛型是自然协变的,这将编译:
List<Integer> listOfInts = new ArrayList<>();
List<Number> listOfNums = listOfInts;
listOfNums.add(Double.valueOf(1.0));
int i = listOfInts.get(0);
但是,用你自己的眼睛跟随,你会意识到代码是一种步行类型的违规行为。它将一个非整数推入一个整数列表中。这就是为什么选择协变或逆变会关闭大门。如果您选择协方差,则 add 方法将被禁用*1:
List<? extends Number> list = new ArrayList<Integer>(); //legal
list.add(Integer.valueOf(5)); // will not compile
同样,如果您选择逆变, add 效果很好,但get被禁用。类型系统意义上的“禁用”:您可以调用它。但表达式list.get(i)将是 Object 类型:
List<? super Integer> list = new ArrayList<Number>(); // legal
list.add(Integer.valueOf(5)); // legal
Integer i = list.get(0); // won't compile
Object o = list.get(0); // this will.
对于“写入”并不完全清楚的类,很难看出为什么Class<Annotation> c = SomeSpecificAnno.class;编译失败,但确实如此,这是重要的实现之一。
你为什么在这里使用反射?
您可以在 Java 中创建类文字。这很好用:
Class<? extends Number> c = Integer.class;
那是真正的java:您可以.class在任何类型的末尾粘贴,这将是 type 的表达式java.lang.Class,事实上,它是 type Class<TheExactThing>。所以:
private static Class<? extends Annotation> getAnnotationType() {
return Retention.class;
}
工作和编译非常好。我必须更新返回类型,因为正如我上面所解释的,返回j.l.Class表示指定返回的Retention方法的注释的实例与从指定返回字符串的方法返回整数Class<Annotation>一样破碎。
答案
如果您的代码示例java.lang.annotation.Retention用作替代品,但此处的实际字符串是您在编译时不知道的动态值,则该return Retention.class;选项不在表中,则:
private static Class<? extends Annotation> getAnnotationType(String fqn) throws ClassNotFoundException {
return Class.forName(fqn).asSubclass(Annotation.class);
}
再次强调,除非别无他法,否则不要使用反射,并且如果您在字符串常量中有该类,通常您不需要反射。
*1 ) 您可以调用 add,但只能使用空文字;list.add(null);编译,因为 null 是任何类型的有效值。然而,这当然不是特别有用。