我想扩展一些处理注释的代码,以支持 Checker 框架中的
Nullable
注释。令我惊讶的是,这不起作用。经过进一步检查,即使使用 @Retention(RUNTIME)
进行元注释,Checker Framework 注释似乎也从未被反射选择。
class AnnotationTest {
@javax.annotation.Nullable
Object javax;
@jakarta.annotation.Nullable
Object jakarta;
@org.checkerframework.checker.nullness.qual.Nullable
Object checker;
@Test
void annotations() {
final Field[] fields = getClass().getDeclaredFields();
for (final Field field : fields) {
System.out.println(field.getName() + " annotated with " + Arrays.toString(field.getDeclaredAnnotations()));
}
}
}
输出:
javax annotated with [@javax.annotation.Nullable()]
jakarta annotated with [@jakarta.annotation.Nullable()]
checker annotated with []
这是一个Spring Boot项目,使用IntelliJ或Gradle执行测试并不重要。为什么其他注释都被选中,而 Checker Framework 注释却没有被选中?
为了完整起见,这里是注释的定义:
@SubtypeOf({})
@ImplicitFor(literals = LiteralKind.NULL, typeNames = java.lang.Void.class)
@DefaultInUncheckedCodeFor({TypeUseLocation.RETURN, TypeUseLocation.UPPER_BOUND})
@Documented
@Retention(RetentionPolicy.RUNTIME) // <-- retained at runtime!
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
public @interface Nullable {}
在注释方法中尝试以下操作:
@Test
void annotations() {
final Field[] fields = getClass().getDeclaredFields();
for (final Field field : fields) {
// direct annotations
System.out.println(field.getName() + " annotated with " + Arrays.toString(field.getDeclaredAnnotations()));
// type-use annotations
AnnotatedType annotatedType = field.getAnnotatedType();
System.out.println("Type-use annotations on " + field.getName() + ": " + Arrays.toString(annotatedType.getDeclaredAnnotations()));
}
}
就在那里。您问错了节点。
javax.annotation.Nullable
和checker的nullable是完全不同的。这是一个总体趋势:大约有 15 个带有无效注释的框架,但没有一个是相同的。真是一团糟。
就其价值而言,Checkerframework 更正确,而大多数其他框架则不太正确。他们错误的原因可能是无能(嘿,这个东西比看起来更复杂),但更可能的是:向后兼容性。 Java 1.5从注解开始;目标
TYPE_USE
在当时还不算什么。后来(1.8?),引入了TYPE_USE。 Checkerframework 要么在 TYPE_USE 可用后启动,要么进行向后不兼容的更新以移动到它。 javax/jakarta 的 nonnull 早于它(或者是由不知道自己在做什么的人设计的/对非 null 在概念上的含义有截然不同的想法 - 但我认为它早于它)。
这里有两种完全不同的无效概念:TYPE_USE
@Target(ElementType.TYPE_USE)
。这是“新的”。这意味着给定:
@Nullable private String foo;
@Nullable
适用的节点只是
String
部分。字段 这里没有注释。一点也没有。。该字段有一个类型并且 that 有一个注释。 null 这个概念是对类型系统本身的扩展。任何类型都有无效状态。
这种观点的缺点是它在泛型方面很复杂。就像用泛型表达“数字列表”有 4 种不同的方式一样:
List<? extends Number>
List<? super Number>
List<Number>
List
“这个类型必然是非空的”——写的时候,代码是 *“这个类型必然是可以为空的”
ElementType.FIELD
@Nullable private String foo;
该字段有注释,但其类型没有。
javax/jakarta 无效注释没有
@Target
注释。这意味着他们的目标是“除了类型参数之外的所有内容”。这本身就是愚蠢的设计(
@Nullable T
是明智的。想一想;给定一个Map<@NonNull String, @NonNull Integer>
,那么,地图的get()
方法的返回类型是V
,所以,那就是 @NonNull Integer
那么,这是错误的,因为
get()
可以返回null
,即使它的“值类型”是一个非空概念,因为null
用于传达“找不到密钥”。正确的无效注释有 public @Nullable V get(Object key)
.更一般地,类型使用只是“正确的”,因为您当然可以拥有包含可为空字符串的非可为空集合的可为空列表。你可以用 TYPE_USE
来表达,但没有它就无法表达。
这是对无效性的完全不同的看法。您只是注释字段/方法的返回值/参数是否允许为空。无法详细说明。您无法传达包含可为空字符串的不可为空集合列表的概念。但是很简单。
FIELD
TYPE_USE
之间的冲突类型使用和其他目标的问题是含糊不清。在:
@Hi
class Foo {}
没有任何歧义。
@Hi
适用于类定义。相比之下:
class Example {
@Hi String field;
@Hi int method() {return 0;}
void method2(@Hi String arg) {}
所有 3 个
@Hi
注释都是不明确的。是适用于字段/方法/参数,还是适用于字段类型/方法返回类型/方法参数类型?
注释规则规定,在不明确的情况下,它直接应用于字段/方法/参数,以保持与没有 TYPE_USE 的 java 1.5 的向后兼容。 除非注释的@Target
表明这无效;在这种情况下,编译器会将注释视为应用于该类型。
javax.annotation.Nullable
适用于字段,而 checkerframework 的
@Nullable
适用于类型。你能看到吗?
field.getAnnotatedType().getDeclaredAnnotations()
就是您要找的。