可能看起来像是这些声明
public <T> @Nullable Set<T> getSomeSet(T something)
public @Nullable <T> Set<T> getSomeSet(T something)
在语法上是不同的,即在第一个变体中,注释绑定到返回类型.显然,《月食》遵循了这个 idea .
然而,grammar of the specification将两者都显示为方法声明的一部分:
MethodDeclaration:
{MethodModifier} MethodHeader MethodBody
MethodHeader:
Result MethodDeclarator [Throws]
TypeParameters {Annotation} Result MethodDeclarator [Throws]
MethodDeclarator:
Identifier ( [ReceiverParameter ,] [FormalParameterList] ) [Dims]
MethodModifier个
MethodModifier:
(one of)
Annotation public protected private
abstract static final synchronized native strictfp
请注意,TypeParameters
和Result
之间的{Annotation}
是MethodHeader
的一部分.
然后这Java Language Specification, §9.7.4个人说:
批注可能出现在程序中的语法位置,在那里它可能合理地应用于声明、类型或两者.
[.]
Java编程语言的语法明确地将这些位置的注释视为声明的修饰符(§8.3),但这纯粹是一个语法问题.批注是应用于声明还是应用于已声明实体的类型--因此,批注是声明批注还是类型批注--取决于批注接口[…]的适用性
我理解这一点的方式是,语法位置不应该重要,重要的是批注的适用性,由@Target
批注或其缺失给出.这也是javac
处理这件事的方式,不同于Eclipse:
public class AnnotationExample {
public static void main(String[] args) throws NoSuchMethodException {
Method m = AnnotationExample.class.getMethod("methodName");
System.out.println("method: " + Arrays.toString(m.getAnnotations()));
System.out.println("return type: "
+ Arrays.toString(m.getAnnotatedReturnType().getAnnotations()));
}
@Retention(RetentionPolicy.RUNTIME) @Target({TYPE_USE, METHOD}) @interface A {}
@Retention(RetentionPolicy.RUNTIME) @Target({TYPE_USE, METHOD}) @interface B {}
public static @A <T extends Enum<T>> @B Set<T> methodName() {
return null;
}
}
当编译为javac
时,它将打印
method: [@AnnotationExample$A(), @AnnotationExample$B()]
return type: [@AnnotationExample$A(), @AnnotationExample$B()]
但在使用Eclipse编译时,它会打印
method: [@AnnotationExample.A()]
return type: [@AnnotationExample.A(), @AnnotationExample.B()]
表示Eclipse仅将类型参数声明后面的批注视为返回类型的批注.因此,将B
‘S声明更改为@Target(METHOD)
会在Eclipse中产生编译错误.
此行为与未给出@Target
注释时假定有效的目标交互:
从Java 8到Java 13,the specification looked like
如果在批注类型T
的声明中不存在类型为java.lang.annotation.Target
的批注,则T
适用于除类型参数声明之外的所有声明上下文,并且不适用于任何类型上下文.
从Java开始,17,the specification looks like
如果在注释接口A
的声明中不存在类型为java.lang.annotation.Target
的注释,则A
适用于所有声明上下文,而不适用于任何类型上下文.
因此,这看起来像是一个直接的更改,现在还允许在没有给出@Target
的情况下将类型参数声明作为目标,这不会影响我们的场景.
如果没有中间版本,14 to 16:
如果在注释接口A的声明中没有出现类型为java.lang.Annotation.Target的注释,则A适用于所有9个声明上下文和所有16个类型上下文.
(在Java 16中,有10个声明上下文和17个类型上下文)
假设类型参数声明后面的批注仅是类型上下文,这将直接影响编译器.由于这比后续版本更宽松,编译器供应商对此感到惊讶并不得不在更新中提供更多限制行为是可以理解的.
(请注意,javac
通过从一开始就没有实现这些更改来躲避这一点,JDK-21是第一个实现自Java 17年以来指定的行为的,见JDK-8309743)
重要的是要记住,虽然不应该发生编译器错误,但规范更改仍然会影响判断代码时报告的注释内容.当您想要能够注释类型而不是方法时,您需要显式的@Target
,包括TYPE_USE
.