java jsr
类型注释和示例用例的好处。
Java SE 5中的Java中引入了注释,它提供了一种语法元数据形式,可以将其添加到程序构造中。 注释可以在编译时进行处理,它们对代码的操作没有直接影响。 但是,它们有许多用例。 例如,他们可以在编译时为开发人员生成信息性消息,检测错误或抑制警告。 此外,可以处理批注以生成可用于修改带批注代码的Java源文件或资源。 后者有助于减少应用程序必须维护的配置资源量。
JSR 308 (关于Java类型的注释)已作为Java SE 8的一部分被合并。此JSR建立在现有注释框架的基础上,允许类型注释成为语言的一部分。 从Java SE 8开始,除了Java声明中的所有现有用法外,注释还可以应用于类型。 这意味着注释现在可以应用于指定类型的任何地方,包括在类实例创建,类型转换,接口的实现以及throws
子句的指定期间。 这使开发人员可以在更多地方应用注释的好处。
类型注释在许多情况下很有用,最显着的是加强类型,这可以帮助减少代码中的错误数量。 可以编写编译器检查器来验证带注释的代码,并在代码不符合特定要求时通过生成编译器警告来实施规则。 Java SE 8没有提供默认的类型检查框架,但是可以编写用于类型检查的自定义注释和处理器。 还可以下载许多类型检查框架,这些框架可以用作Java编译器的插件来检查和强制执行已注释的类型。 类型检查框架包括类型注释定义和一个或多个可插入模块,这些模块与编译器一起用于注释处理。
本文从对注释的简要概述开始,然后您将学习如何将注释应用于Java类型,编写类型注释以及使用编译时插件进行类型检查。 阅读本文之后,您将能够使用类型注释在Java源代码中强制更强的键入。
内置注释概述
注释可以在代码中轻松识别,因为注释名称以@
字符开头。 注释对代码操作没有直接影响,但是在处理时,它们可以使注释处理器生成文件或提供参考消息。
需要信息吗?
注释对代码操作没有直接影响,但是在处理时,它们可以使注释处理器生成文件或提供参考消息。
以最简单的形式,可以在Java源代码中放置一个注释,以指示编译器必须对被注释的组件执行特定的“检查”,以确保代码符合指定的规则。
Java带有一组基本的内置注释。 可以直接使用以下Java批注:
-
@Deprecated
:指示不再使用标记的元素。 通常,已经创建了另一个元素,该元素封装了标记元素的功能,并且不再支持标记元素。 当在源代码中找到被标记的元素时,此注释将导致编译器生成警告。 -
@Override
:指示标记的方法将覆盖超类中定义的另一个方法。 如果标记的方法未覆盖超类中的方法,则编译器将生成警告。 -
@SuppressWarnings
:指示如果被标记的元素生成警告,则编译器应禁止显示这些警告。 -
@SafeVarargs
:指示标记的元素不会通过其varargs
参数执行潜在的不安全操作。 使编译器抑制与varargs
相关的未经检查的警告。 -
@FunctionalInterface
:指示类型声明旨在用作功能接口。
清单1显示了使用这些内置注释的几个示例。
@Override
public void start(Stage primaryStage) {
...
}
@Deprecated
public void buttonAction(ActionEvent event){
...
}
清单1
类型注释的用例
注释可以存在于任何Java类型声明或表达式上,以帮助强制进行更强的键入。 以下用例说明了类型注释在哪些地方可以具有很大的价值。
生成新对象。 创建新对象时,类型注释可以提供静态验证,以帮助强制注释在对象构造函数上的兼容性。 例如:
Forecast currentForecast =
new @Interned Forecast();
泛型和数组。 泛型和数组是类型注释的理想选择,因为泛型和数组可以帮助限制期望用于这些对象的数据。 类型注释不仅可以使用编译器检查以确保将正确的数据类型存储在这些元素中,而且注释还可以用作可视化的提醒开发人员,以指示变量或数组的意图,例如:
@NonEmpty Forecast []
类型转换。 可以对类型转换进行批注,以确保在类型转换中保留带批注的类型。 它们也可以用作限定符,以警告非预期的转换用途,例如:
@Readonly Object x; …
(@Readonly Date) x …
要么
Object myObject =
(@NotNull Object) obj
遗产。 强制使用类扩展或实现的适当类型或对象可以大大减少应用程序代码中的错误数量。 清单2包含一个关于实现子句的类型注释的示例。
class MyForecast<T> implements @NonEmpty List< @ReadOnly T>
清单2
例外情况。 可以注明异常,以确保它们符合某些条件,例如:
catch (@Critical Exception e) {
...
}
接收器。 通过在参数列表中显式列出方法的接收者参数,可以对它进行注释。 清单3展示了方法接收器参数上的类型注释的演示。
class Weather {
...
void tempCalc(@ReadOnly Weather this){}
...
}
清单3
应用类型注释
类型注释可以通过多种方式应用于类型。 通常,它们直接放置在它们所应用的类型之前。 但是,对于数组,应将它们放在类型的相关部分之前。 例如,在以下声明中,数组应为只读:
Forecast @Readonly [] fiveDay =
new Forecast @Readonly [5];
在注释数组和数组类型时,将注释放置在正确的位置很重要,以便将其应用于数组中的预期元素。 这里有一些例子:
- 注释
int
类型:@ReadOnly int [] nums;
- 注释数组类型
int[]
:int @ReadOnly [] nums;
- 注释数组类型
int[][]
:int @ReadOnly [][] nums;
- 注释类型
int[]
,它是int[][]
的组件类型:int [] @ReadOnly [] nums;
使用可用的类型注释
要强制进行更严格的类型检查,您必须具有一组适当的注释,这些注释可用于对类型强制执行某些条件。 因此,今天有许多类型检查注释可供使用,包括Checker Framework可用的注释。
Java SE 8不包括任何特定于类型的注释,但是诸如Checker Framework之类的库包含可以应用于类型以验证某些条件的注释。 例如,Checker Framework包含@NonNull
批注,该批注可以应用于类型,以便在编译时被验证为不为null
。 Checker Framework还包含@Interned
批注,该批注指示变量引用对象的规范表示。 以下是Checker Framework可用的其他一些注释示例:
-
@GuardedBy
:指示仅当持有给定锁时才可以访问其值的类型 -
@Untainted
:指示仅包含未@Untainted
可信值的类型 -
@Tainted
:表示可能仅包含污染的,不可信的值的类型;@Untainted
的超类型 -
@Regex
:指示字符串上的有效正则表达式
要使用Checker框架中的批注,必须下载框架,然后将批注源文件添加到CLASSPATH
,或者(如果使用Maven的话)将Checker Framework添加为依赖项。
如果使用的是NetBeans等IDE,则可以使用“添加依赖项”对话框轻松将Checker Framework添加为依赖项。 例如, 图1显示了如何向Maven项目添加依赖项。
图1
一旦将依赖项添加到应用程序中,就可以通过将它们导入类中来开始在类型上使用批注,如清单4所示。
import checkers.interning.quals.Interned;
import checkers.nullness.quals.NonNull;
...
@ZipCode
@NonNull
String zipCode;
清单4
如果您有任何现有实现都不满足的要求,则还可以选择创建自定义注释以满足您的需求。
定义自定义注释
批注是接口的一种形式,批注类型定义看起来与接口定义非常相似。 两者之间的区别在于,注释类型定义包括以@
字符为前缀的interface关键字。 注释定义还包括注释本身,这些注释指定有关类型定义的信息。 定义注释时可以使用以下注释列表:
-
@Retention
:指定注释的存储方式。 选项包括CLASS(默认值;在运行时不可访问),RUNTIME
(在运行时可用)和SOURCE
(在编译类之后,将忽略注释)。 -
@Documented
:将注释标记为包含在Javadoc中。 -
@Target
:指定可以将注释应用到的上下文。 包含类型为java.lang .annotation.ElementType[]
的单个元素value
。 -
@Inherited
:将注释标记为被注释的类的子类继承。
标准声明注释和类型注释的定义看起来非常相似。 关键区分@Target
在@Target
规范中,它表示可以应用特定批注的元素的类型。 声明注释以字段为目标,而类型注释以类型为目标。
声明注释可以包含以下元注释:
@Target(ElementType.FIELD)
类型注释必须包含以下元注释:
@Target(ElementType.TYPE_USE)
如果类型注释要以类型参数作为目标,则注释必须包含以下元注释:
@Target
(ElementType.TYPE_PARAMETER)
类型注释可以应用于多个上下文。 在这种情况下,可以在列表中指定多个ElementType
。 如果在列表中多次指定相同的ElementType
,则将显示编译时错误。
清单5显示了@NonNull
类型注释定义的完整清单。 在清单中, @NonNull
的定义包括两个目标,这意味着可以将注释应用于类型或类型参数。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@TypeQualifier
public @interface NonNull {
}
清单5
与标准声明注释类似,类型注释也可以包含参数,并且它们可以包含默认值。 要为类型注释指定参数,请在注释接口内添加其声明。 清单6演示了带有一个参数的类型注释:一个由zip
标识的字符串字段。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@TypeQualifier
public @interface ZipCode {
String zip() default "60605";
}
清单6
注释可以同时应用于字段和类型。 例如,如果将@Foo
的注释应用于变量声明,则如果@Target
包含两个元素,则也可以同时将其应用于类型声明。 这种情况可能类似于以下声明,该声明同时适用于String
类型和变量myString
:
@Foo String myString;
处理类型注释
在将类型注释应用于代码之后,必须使用编译器插件来相应地处理注释。 如前所述,注释对应用程序代码没有任何操作影响。 处理器在解析代码时执行魔术操作,并在遇到注释时执行某些任务。
如果编写自定义注释,则还必须编写自定义编译器插件来处理它们。 JSR 269 (可插拔注释处理API)为开发自定义注释处理器提供支持。 开发注释处理器不在本文讨论范围之内,但是可插拔注释处理API使其易于实现。 也有可供下载的注释处理器,例如Checker Framework处理器。
使用类型限定符编译器插件。 Checker Framework是一个库,即使您使用的Java是较早的版本,也可以在您的应用程序中使用它。 该框架包含许多可供使用的类型注释,以及可以在编译代码时指定的注释处理器。 Java SE 8中的注释支持使您可以使用第三方库(例如Checker Framework),从而可以轻松地将预先构建的类型注释合并到新代码和现有代码中。
以前,我们看到了如何将Checker Framework合并到项目中,以便利用其附带的类型注释。 但是,如果您还不使用自定义批注处理器,则不会处理这些批注,它们仅对文档有用。
Checker框架针对该框架可用的每个类型注释包含许多自定义处理器。 将框架安装到计算机上之后,可以使用与框架一起打包的自定义javac
编译器来编译带注释的应用程序。
要使用Checker Framework,只需下载.zip文件并将其解压缩到您的计算机中即可。 (可选)更新您的执行路径或创建别名,以使其易于执行Checker Framework二进制文件。 安装后,可以通过执行类似于清单7所示的命令来验证该框架。
java -jar binary/checkers.jar -version
javac 1.8.0-jsr308-1.7.1
清单7
它有助于知道可以使用哪些注释,因此Checker Framework的用户应首先查看框架文档以阅读有关它们的信息。 Checker框架将类型注释称为限定符 。 熟悉可用的限定符后,请确定将哪些限定符合并到现有应用程序中的类型中可能很有用。 如果您正在编写新的应用程序,请对类型应用限定符以充分利用这些好处。
要检查您的代码,必须将编译器定向到用于类型检查的处理器。 这可以通过正常执行自定义javac
并指定–processor
标志以及标准处理器名称来完成。
例如,如果使用@NonNull
批注,则在编译代码时必须指定空处理器。 如果已安装Checker Framework,请使用随框架一起分发的自定义javac
,并指定checkers.nullness.NullnessChecker
处理器来处理注释。
清单8包含一个使用@NonNull
批注的示例类。 要编译此类,请使用清单9中的命令。 如果清单8中显示的类已编译,则不会发出警告。
import checkers.nullness.quals.*;
public class WeatherTracker {
public WeatherTracker(){}
void obtainForecast() {
@NonNull Object ref;
}
}
清单8
javac -processor checkers.nullness.NullnessChecker
WeatherTracker.java
清单9
但是,将null
分配给带注释的变量声明将导致null
检查器提供警告。 清单10显示了修改后的类, 清单11显示了编译器将产生的警告。
import checkers.nullness.quals.*;
public class WeatherTracker {
public WeatherTracker(){}
void obtainForecast() {
@NonNull Object ref = null;
}
}
清单10
javac -processor checkers.nullness.NullnessChecker
WeatherTracker.java
WeatherTracker.java:7: error: incompatible types in assignment.
@NonNull Object ref = null;
^
found : null
required: @UnknownInitialization @NonNull Object
1 error
清单11
您可以使用标准JDK安装并运行checkers.jar
,而不是使用自定义javac
二进制文件,该工具将利用Checker编译器而不是标准编译器。 清单12展示了对checkers .jar
的调用,而不是自定义javac
的调用。
java -jar binary/checkers.jar
-processor checkers.nullness.NullnessChecker WeatherTracker.java
WeatherTracker.java:7: error: incompatible types in assignment.
@NonNull Object ref = null;
^
found : null
required: @UnknownInitialization @NonNull Object
1 error
清单12
Checker框架包含有关将自定义javac
命令添加到CLASSPATH
并创建别名的说明,并且它描述了使框架轻松集成到编译过程中的更多方法。 有关完整的详细信息,请参阅文档。
一次使用多个处理器进行编译。 如果您希望在编译时指定多个处理器怎么办? 通过自动发现,在使用Checker Framework进行编译时可以使用多个处理器。 要启用自动发现,必须在CLASSPATH
放置一个名为META-INF/services/javax.annotation.processing.Processor
的配置文件。 该文件必须包含将要使用的每个Checker插件的名称,每行一个。 使用自动发现时,即使未指定–processor
标志, javac
编译器也将始终运行列出的Checker插件。
要禁用自动发现,请将–proc:none
命令行选项传递给javac
。 此选项禁用所有注释处理。
使用Maven插件。 提供了一个插件,该插件允许Checker Framework成为任何Maven项目的一部分。 要使用该插件,请修改项目对象模型(POM)以指定Checker Framework存储库,将依赖项添加到您的项目中,然后将该插件附加到项目构建生命周期中。 清单13 , 清单14和清单15展示了完成这些任务中的每一个的示例。
<!-- Add repositories to POM -->
<repositories>
<repository>
<id>checker-framework-repo</id>
<url>http://types.cs.washington.edu/m2-repo</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>checker-framework-repo</id>
<url>http://types.cs.washington.edu/m2-repo</url>
</pluginRepository>
</pluginRepositories>
清单13
<!-- Declare the dependency within the POM -->
<dependencies>
...
<dependency>
<groupId>edu.washington.cs.types.checker</groupId>
<artifactId>checker-framework</artifactId>
<version>1.7.0</version>
</dependency>
...
</dependencies>
清单14
<!-- Plugin to be specified in POM-->
<plugin>
<groupId>types.checkers</groupId>
<artifactId>checkers-maven-plugin</artifactId>
<version>1.7.0</version>
<executions>
<execution>
<!-- run the checkers after compilation;
this can also be any later phase -->
<phase>process-classes</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- required configuration options -->
<!-- a list of processors to run -->
<processors>
<processor>checkers.nullness.NullnessChecker</processor>
<processor>checkers.interning.InterningChecker</processor>
</processors>
<!-- optional configuration options go here -->
</configuration>
</plugin>
清单15
使用Maven插件可以很容易地将Checker Framework绑定到项目,以在IDE中使用。 例如,如果在NetBeans Maven项目上配置了插件,则每次在NetBeans中构建项目时,Checker框架都会处理注释。
分发包含类型注释的代码
要使用特定的类型注释,其声明必须在CLASSPATH
。 分发包含类型注释的应用程序时,也是如此。 要编译或运行包含类型注释的源代码,注释声明类必须CLASSPATH
存在于CLASSPATH
。
如果您编写了自定义注释,则它们可能已经是应用程序源代码的一部分。 如果不是,则应将包含这些注释声明的JAR文件与代码分发一起打包。 Checker框架包括一个JAR文件checkers-quals.jar
,其中包括分布式限定符的声明(注释)。 如果在应用程序中使用Checker Framework批注,则应将此JAR文件与发行版打包在一起。
结论
Java SE 8添加了对类型注释的支持。 类型注释可以提供更强大的类型检查系统,从而减少代码中错误和错误的数量。 使用类型注释的应用程序也向后兼容,因为注释不会影响运行时操作。
开发人员可以选择创建自定义类型注释,也可以使用第三方解决方案中的注释。 Checker框架是最著名的类型检查框架之一,可以与Java SE 8或更低版本一起使用。 要开始使您的应用程序不易出错,请查看Checker Framework文档。
最初发表于2014年3月/ 4月的Java Magazine 。 立即订阅 。
关于作者
Josh Juneau 是应用程序开发人员,系统分析师和DBA。 他主要使用Java,PL / SQL和Jython / Python进行开发。 他管理 Jython月刊 , Jython Podcast 和 Jython网站 。 他撰写了《 Java EE 7食谱:一种问题解决方法》 (Apress,2013年)和 《 Java EE 7简介:新内容》 (Apress,2013年)。
(1)最初发表于Java Magazine 2014年3月/ 4月版
(2)版权所有©[2014] Oracle。
翻译自: https://jaxenter.com/jsr-308-explained-java-type-annotations-107706.html
java jsr