区分静态分析和运行时分析。对内部内容使用静态分析,对代码的公共边界使用运行时分析。
对于不应该为null的东西:
Runtime check: Use "if (x == null) ..." (zero dependency) or @javax.validation.NotNull (with bean validation) or @lombok.NonNull (plain and simple) or guavas Preconditions.checkNotNull(...)
Use Optional for method return types (only). Either Java8 or Guava.
Static check: Use an @NonNull annotation
Where it fits, use @...NonnullByDefault annotations on class or package level. Create these annotations yourself (examples are easy to find).
Else, use @...CheckForNull on method returns to avoid NPEs
这应该会得到最好的结果:IDE中的警告、Findbugs和checkerframework的错误、有意义的运行时异常。
不要期望静态检查是成熟的,它们的命名不是标准化的,不同的库和ide对待它们是不同的,忽略它们。JSR305 java .annotations。*类看起来像标准的,但它们不是,它们在Java9+中会导致拆分包。
一些注释解释:
Findbugs/spotbugs/jsr305 annotations with package javax.validation.* clash with other modules in Java9+, also possibly violate Oracle license
Spotbugs annotations still depends on jsr305/findbugs annotations at compiletime (at the time of writing https://github.com/spotbugs/spotbugs/issues/421)
jetbrains @NotNull name conflicts with @javax.validation.NotNull.
jetbrains, eclipse or checkersframework annotations for static checking have the advantage over javax.annotations that they do not clash with other modules in Java9 and higher
@javax.annotations.Nullable does not mean to Findbugs/Spotbugs what you (or your IDE) think it means. Findbugs will ignore it (on members). Sad, but true (https://sourceforge.net/p/findbugs/bugs/1181)
For static checking outside an IDE, 2 free tools exist: Spotbugs(formerly Findbugs) and checkersframework.
The Eclipse library has @NonNullByDefault, jsr305 only has @ParametersAreNonnullByDefault. Those are mere convenience wrappers applying base annotations to everything in a package (or class), you can easily create your own. This can be used on package. This may conflict with generated code (e.g. lombok).
Using lombok as an exported dependency should be avoided for libraries that you share with other people, the less transitive dependencies, the better
Using Bean validation framework is powerful, but requires high overhead, so that's overkill just to avoid manual null checking.
Using Optional for fields and method parameters is controversial (you can find articles about it easily)
Android null annotations are part of the Android support library, they come with a whole lot of other classes, and don't play nicely with other annotations/tools
在Java9之前,这是我的建议:
// file: package-info.java
@javax.annotation.ParametersAreNonnullByDefault
package example;
// file: PublicApi
package example;
public interface PublicApi {
Person createPerson(
// NonNull by default due to package-info.java above
String firstname,
String lastname);
}
// file: PublicApiImpl
public class PublicApiImpl implements PublicApi {
public Person createPerson(
// In Impl, handle cases where library users still pass null
@Nullable String firstname, // Users might send null
@Nullable String lastname // Users might send null
) {
if (firstname == null) throw new IllagalArgumentException(...);
if (lastname == null) throw new IllagalArgumentException(...);
return doCreatePerson(fistname, lastname, nickname);
}
@NonNull // Spotbugs checks that method cannot return null
private Person doCreatePerson(
String firstname, // Spotbugs checks null cannot be passed, because package has ParametersAreNonnullByDefault
String lastname,
@Nullable String nickname // tell Spotbugs null is ok
) {
return new Person(firstname, lastname, nickname);
}
@CheckForNull // Do not use @Nullable here, Spotbugs will ignore it, though IDEs respect it
private Person getNickname(
String firstname,
String lastname) {
return NICKNAMES.get(firstname + ':' + lastname);
}
}
注意,当可以为空的方法参数被解引用时,没有办法让Spotbugs引发警告(在撰写本文时,Spotbugs是3.1版)。也许checkerframework可以做到。
遗憾的是,这些注释并没有区分具有任意调用点的库中的公共方法和每个调用点都可以已知的非公共方法。因此,“指示不需要null,但准备传递null”的双重含义在单个声明中是不可能实现的,因此上面的示例为接口和实现提供了不同的注释。
对于分离接口方法不实用的情况,以下方法是一种折衷方案:
public Person createPerson(
@NonNull String firstname,
@NonNull String lastname
) {
// even though parameters annotated as NonNull, library clients might call with null.
if (firstname == null) throw new IllagalArgumentException(...);
if (lastname == null) throw new IllagalArgumentException(...);
return doCreatePerson(fistname, lastname, nickname);
}
这有助于客户端不传递null(编写正确的代码),同时返回有用的错误。