如何在不发出"未选中的投射"警告的情况下安全地将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(当不涉及<>
时)是协变的。任何子类型都是其超类型之一的替身。这:
Number n = Integer.valueOf(5);
是完全合法的爪哇。但在仿制药领域并非如此。如果你想要它,那么,你必须选择加入它: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);
但是,用自己的眼睛跟随,你意识到代码是一种行走类型的违规。它将一个非整数推入整数列表中。这就是为什么选择协方差或逆变会关门的原因。如果选择协方差,则禁用添加方法*1:
List<? extends Number> list = new ArrayList<Integer>(); //legal
list.add(Integer.valueOf(5)); // will not compile
同样,如果您选择逆变,添加效果很好,但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.
对于类,"write"并不完全清楚,很难理解为什么Class<Annotation> c = SomeSpecificAnno.class;
编译失败,但它确实如此,所以,这是重要的实现之一。
你为什么在这里使用反射?
您可以在 java 中创建类文字。这很好用:
Class<? extends Number> c = Integer.class;
这就是真正的java:您可以将.class
粘在任何类型的末尾,这将是类型java.lang.Class
的表达式,实际上,它是Class<TheExactThing>
类型。所以:
private static Class<? extends Annotation> getAnnotationType() {
return Retention.class;
}
工作和编译都很棒。我必须更新返回类型,因为如上所述,返回表示指定返回的方法的Retention
注释的j.l.Class
实例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,但只能使用 null 文本;list.add(null);
编译,因为 null 对于任何类型的都是有效的值。但是,这当然不是特别有用。
由于类型擦除,泛型类型信息在运行时无法再访问。这意味着:Class<?>
和Class<? extends Annotation>
在运行时无法区分 - 因此您无法执行运行时检查以使未选中的投射成为已检查的投射。
这意味着:你必须接受警告(注意:警告不是错误,它只是意味着"这是有问题的,确保你知道你在做什么!)。