我有这样的注释:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface NotNull {}
上课:
class Main{
List<@NotNull String> list;
}
我应该如何使用 Reflection API 解析这个注释?
注释显示在泛型部分内。这立即意味着两件事:
前提条件:注释被注释为
@Target(ElementType.TYPE_USE)
- 或者它根本不可能存在。
如果类型所在的位置不属于签名的一部分(例如,局部变量声明、强制转换、或
new Foo<X>()
中的 X 等),那么 它就消失了,泛型将被删除在这种情况下,根本没有办法引用它,你在运行时再也看不到它了。
如果类型是签名的一部分(它是字段的类型,方法的返回类型,参数的类型,或者您
extends
或implements
的东西),那么它会被保留在运行时,但是,所有获取这些东西的常用方法,例如 someJLReflectFieldInstance.getType()
,总是返回一个 java.lang.Class
实例,并且泛型也从这些实例中删除,所以你不能使用这些方法。
如果您乘坐#1 号船,答案立即结束:不可能。
如果您乘坐 2 号船,这是可能的。首先,找到
getGenericType()
方法 - 一种变体方法,它也返回您想要的类型,但作为 Type
而不是 Class
。对于 j.l.reflect.Field
,该方法是 getGenericType()
。方法返回类型、参数类型等也存在类似的方法。然后意识到这仍然不够 - 这会为您提供 <>
中的内容,但会删除所有注释。继续寻找,您就会找到真正的赢家。对于 j.l.r.Field
,即 getAnnotatedType
。
你现在有了一个
AnnotatedType
,它看起来几乎完全没用:它几乎没有方法。它是一个包罗万象的标记式接口,因为对于 java 中的类型确实有不同的想法。 ?
是这个意义上的一种类型,因为它可以出现在类型出现的地方:List<?>
。 ? extends Map<?, Set<String>>
也是如此。 T
也是如此,String
也是如此,int[]
也是如此,double
也是如此。而且几乎都可以注释。
通常的解决方案是铸造和
instanceof
检查。像 List<@NonNull String>
这样的东西是 java.lang.reflect.AnnotatedParameterizedType
。因此,让我们进行投射并继续。这个接口有 getAnnotatedActualTypeArguments()
方法,它再次返回一个无用的 AnnotatedType
数组:合适;毕竟,它可能是 List<?>
、List<String>
、List<T>
、List<? extends Stuff>
,等等。
@NonNull String
将是 java.lang.reflect.AnnotatedType
的实例。
让我们把它们放在一起。将其放入文件中,编译并运行它:
import java.lang.annotation.*;
import java.util.List;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@interface NonNull {}
class Test {
List<@NonNull String> example;
public static void main(String[] args) throws Exception {
Field f = Test.class.getDeclaredField("example");
AnnotatedParameterizedType listType =
(AnnotatedParameterizedType) f.getAnnotatedType();
AnnotatedType annType = (AnnotatedType)
listType.getAnnotatedActualTypeArguments()[0];
for (Annotation ann : annType.getAnnotations()) {
System.out.println("Annotation found: " + ann);
}
}
}
javac Test.java; java Test
> Annotation found: @NonNull()
注意:如果你知道你在寻找什么,上面的内容并没有那么糟糕,但如果你试图处理可能类型种类的广泛数组(呵呵),你很快就会遇到一大堆混乱的代码。我不直接知道有哪些库可以提供帮助,但您可能想研究一下或写一些东西;为这些东西设置访问者模式并不太困难。
NB2:JDK20-23 及更高版本中引入的各种模式匹配开关构造是专门设计的,以避免这里的“一团糟”;你可以写这样的东西:
switch (f.getAnnotatedType()) {
case AnnotatedParameterizedType apt -> {
// this code runs if f's type is somethng like `List<@NonNull String>`
// ... and variable 'apt' is available, and of type AnnotatedParameterizedType;
}
}
您可以使用此模式匹配开关做很多事情,这只是一个非常简单的示例,说明它可以做什么以及它在这里如何有用。