如何在 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(当不涉及<>时)是协变的。任何子类型都是其超类型之一的替身。这:

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>在运行时无法区分 - 因此您无法执行运行时检查以使未选中的投射成为已检查的投射。

这意味着:你必须接受警告(注意:警告不是错误,它只是意味着"这是有问题的,确保你知道你在做什么!)。

相关内容

最新更新