04-JSR-305支持
15.1.3 JSR-305支持
众所周知,IntelliJ IDEA等IDE工具已经实现了部分代码检查功能,诸如FindBugs、Checkstyle和PMD这样的静态分析工具在Java开发中也得到了广泛应用。虽然这些工具都很强大,但是有一些问题是它们都很难解决的,比如对null值的判断,或者数值为负的情况。
为了解决这些问题,有些静态分析工具试图使用注解来定义相关细节,比如FindBugs和IntelliJ都定义了自己的注解,以标识方法返回为null的情况。虽然注解的使用方法有细微不同,但它们都遵循JSR-305标准。而Kotlin也在最近的版本中添加了对SR-305标准的支持。
在Kotlin中,使用定义的@Nonnull注解可以表示Java类型的可空性,使用@Nonnull注解有以下几种情况:如果@Nonnull(when = ...)值为When.ALWAYS,那么该注解类型会被视为非空;当值为When.MAYBE或When.NEVER时表示可空类型;当值为When.UNKNOWN时,标识强制类型为平台类型。
Kotlin编译器可以从注解库中读取JSR-305注解,而不需要指明该注解的完整路径。从Kotlin 1.1.5版本开始,还可以使用自定义可空限定符。
1.类型限定符别称
如果一个注解类型同时标注有@TypeQualifierNickname与JSR-305@Nonnull(或者其他别称,如@CheckForNull),那么该注解类型自身可以精确地检索可空性,而且具有与该可空性注解相同的含义。代码如下。
@TypeQualifierNickname
@Nonnull(when = When.ALWAYS)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyNonnull {
}
@TypeQualifierNickname
@CheckForNull
@Retention(RetentionPolicy.RUNTIME)
public @interface MyNullable {
}
interface A {
@MyNullable String foo(@MyNonnull String x);
//在Kotlin(严格模式)中:`fun foo(x: String): String?`
String bar(List<@MyNonnull String> x);
//在Kotlin(严格模式)中:`fun bar(x: List<String>!): String!`
}
2.类型限定符默认值
如果@TypeQualifierDefault注解被应用在所标注元素的作用域内,那么该元素定义默认使用可空性注解,该注解类型自身包含的标注有@Nonnull注解(或其别称)与@TypeQualifierDefault (...)注解,后者带有一到多个ElementType值。该ElementType常见的情况有以下几种。
- ElementType.METHOD:用于方法的返回值。
- ElementType.PARAMETER:用于值参数。
- ElementType.FIELD:用于字段。
- ElementType.TYPE_USE:该属性自Kotlin 1.1.60版本起,适用于任何类型,包括类型参数、类型参数的上界与通配符类型。
当类型未标注可空性注解时,默认使用可空性注解,而且该默认值是由与最内层标注所用类型相匹配的ElementType类型限定符的默认注解元素确定的。
@Nonnull
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
public @interface NonNullApi {
}
@Nonnull(when = When.MAYBE)
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER, ElementType. TYPE_USE})
public @interface NullableApi {
}
@NullableApi
interface A {
String foo(String x); // fun foo(x: String?): String?
@NotNullApi //覆盖来自接口的默认值
String bar(String x, @Nullable String y); // fun bar(x: String, y: String?): String
// @NullableApi具有TYPE_USE元素类型
//因此认为List<String> 类型参数是可空的
String baz(List<String> x); // fun baz(List<String?>?): String?
//x参数仍然是平台类型,因为有显式UNKNOWN 标记的可空性注解
String qux(@Nonnull(when = When.UNKNOWN) String x); // fun baz(x: String!): String?
}
除此之外,类型限定符还支持包级的默认可空性。代码如下。
//文件完整路径test/package-info.java
@NonNullApi //默认将test包中的所有类型声明为不可空
package test;
3.@UnderMigration注解
自Kotlin 1.1.60版本开始,可以使用@UnderMigration注解来定义可空性类型限定符的迁移状态,使用该功能时需要引入kotlin-annotations-jvm库。
@UnderMigration(status = ...)注解的状态值指定了编译器处理Kotlin中注解类型的用法。该注解状态主要有以下几种情况。
- MigrationStatus.STRICT:该注解与可空性注解一样,即对不当用法报错并影响注解声明内的类型在Kotlin中的呈现。
- MigrationStatus.WARN:该注解会对不当的用法报警而不是报错,声明内的类型为平台类型。
- MigrationStatus.IGNORE:该注解会使编译器完全忽略可空性注解。
在使用@UnderMigration注解时,还可以配合类型限定符别称与类型限定符默认值一起使用。代码如下。
@Nonnull(when = When.ALWAYS)
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
@UnderMigration(status = MigrationStatus.WARN)
public @interface NonNullApi {
//其他内容
}
//类中的类型是非空的,但是只报警告
//因为@NonNullApi标注了@UnderMigration(status = MigrationStatus.WARN)注解
@NonNullApi
public class Test {}
可空性注解的迁移状态并不会从其类型限定符别称中继承,而是仅仅遵循默认类型限定符的用法。如果默认类型限定符使用了类型限定符别称,而且它们都使用了@UnderMigration注解,那么该注解将使用默认类型限定符的状态。
4.编译器配置
Kotlin使用-Xjsr305编译器标志来检测JSR-305配置,在Kotlin 1.1.50+/1.2 版本中,默认使用-Xjsr305=warn。
- -Xjsr305={strict|warn|ignore}:设置非@UnderMigration注解行为,自定义的可空性限定符,其中@TypeQualifierDefault注解已经被用在很多知名开发库中。当用户更新到JSR-305支持的最新Kotlin版本时,可能需要对旧代码进行手动迁移。自Kotlin 1.1.60版本起,该配置只影响非@UnderMigration注解。
- -Xjsr305=under-migration:{strict|warn|ignore}:自Kotlin 1.1.60版本开始,该配置覆盖@UnderMigration注解相关的行为,使用它可以对库的迁移状态给出不同的错误信息。
- -Xjsr305=@<fq.name>:{strict|warn|ignore}:自Kotlin 1.1.60版本起,使用该配置将覆盖单个注解的行为,其中<fq.name>是该注解的完整限定类名。不同的注解可以多次出现,这对于管理特定库的状态迁移是非常有用的。
在上述配置中,strict、warn与ignore值的含义与MigrationStatus注解中的值含义是相同的,而且只有strict模式会影响注解声明类型在Kotlin中的呈现。但是,系统内置的JSR-305注解@Nonnull、@Nullable与@CheckForNull总是启用的并影响所注解的声明在Kotlin中的呈现,即使修改-Xjsr305编译器配置标志也是不会有影响的。例如,将-Xjsr305=ignore -Xjsr305= under-migration:ignore [email protected]:warn添加到编译器参数中时,编译器会对由@org.library.MyNullable标注的不当用法生成警告,而忽略其他所有JSR-305注解。