我应该使用哪个 @NotNull Java 注释?

我希望使代码更具可读性,并使用诸如 IDE 代码检查和 / 或静态代码分析(FindBugs 和 Sonar)之类的工具来避免 NullPointerExceptions。许多工具似乎彼此不兼容,它们的@NotNull / @NonNull / @Nonnull注释在我的代码中列出了所有这些工具将很容易阅读。关于哪个是 “最佳” 的任何建议?这是我发现的等效注释的列表:

  • javax.validation.constraints.NotNull
    为运行时验证而非静态分析而创建。
    文件资料

  • edu.umd.cs.findbugs.annotations.NonNull
    Findbugs静态分析使用,因此也用于 Sonar(现在为Sonarqube
    文件资料

  • javax.annotation.Nonnull
    这可能也可以与 Findbugs 一起使用,但是JSR-305处于非活动状态。 (参见: 什么是 JSR 305 的状态?

  • org.jetbrains.annotations.NotNull
    由 IntelliJ IDEA IDE 进行静态分析。
    文件资料

  • lombok.NonNull
    用于控制Lombok 项目中的代码生成。
    占位符注释,因为没有标准。
    文档

  • android.support.annotation.NonNull
    支持注释包提供的 Android 中可用的标记注释
    文件资料

  • org.eclipse.jdt.annotation.NonNull
    Eclipse 用于静态代码分析
    文件资料

答案

public @interface NonNull {}
public @interface Nullable {}
FIELD   METHOD  PARAMETER LOCAL_VARIABLE 
android.support.annotation      X       X       X   
edu.umd.cs.findbugs.annotations X       X       X         X
org.jetbrains.annotation        X       X       X         X
lombok                          X       X       X         X
javax.validation.constraints    X       X       X
package android.support.annotation;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER})
public @interface NonNull {}
package edu.umd.cs.findbugs.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NonNull {}
package org.eclipse.jdt.annotation;
@Retention(CLASS)
@Target({ TYPE_USE })
public @interface NonNull {}
package org.jetbrains.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NotNull {String value() default "";}
package javax.annotation;
@TypeQualifier
@Retention(RUNTIME)
public @interface Nonnull {
    When when() default When.ALWAYS;
    static class Checker implements TypeQualifierValidator<Nonnull> {
        public When forConstantValue(Nonnull qualifierqualifierArgument,
                Object value) {
            if (value == null)
                return When.NEVER;
            return When.ALWAYS;
        }
    }
}
package org.checkerframework.checker.nullness.qual;
@Retention(RUNTIME)
@Target({TYPE_USE, TYPE_PARAMETER})
@SubtypeOf(MonotonicNonNull.class)
@ImplicitFor(
    types = {
        TypeKind.PACKAGE,
        TypeKind.INT,
        TypeKind.BOOLEAN,
        TypeKind.CHAR,
        TypeKind.DOUBLE,
        TypeKind.FLOAT,
        TypeKind.LONG,
        TypeKind.SHORT,
        TypeKind.BYTE
    },
    literals = {LiteralKind.STRING}
)
@DefaultQualifierInHierarchy
@DefaultFor({TypeUseLocation.EXCEPTION_PARAMETER})
@DefaultInUncheckedCodeFor({TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND})
public @interface NonNull {}
package android.support.annotation;
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD})
public @interface Nullable {}
package edu.umd.cs.findbugs.annotations;
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
@Retention(CLASS)
public @interface Nullable {}
package org.eclipse.jdt.annotation;
@Retention(CLASS)
@Target({ TYPE_USE })
public @interface Nullable {}
package org.jetbrains.annotations;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface Nullable {String value() default "";}
package javax.annotation;
@TypeQualifierNickname
@Nonnull(when = When.UNKNOWN)
@Retention(RUNTIME)
public @interface Nullable {}
package org.checkerframework.checker.nullness.qual;
@Retention(RUNTIME)
@Target({TYPE_USE, TYPE_PARAMETER})
@SubtypeOf({})
@ImplicitFor(
    literals = {LiteralKind.NULL},
    typeNames = {java.lang.Void.class}
)
@DefaultInUncheckedCodeFor({TypeUseLocation.RETURN, TypeUseLocation.UPPER_BOUND})
public @interface Nullable {}
package lombok;
@Retention(CLASS)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE})
public @interface NonNull {}
package javax.validation.constraints;
@Retention(RUNTIME)
@Target({ FIELD, METHOD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Constraint(validatedBy = {})
public @interface NotNull {
    String message() default "{javax.validation.constraints.NotNull.message}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default {};
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        NotNull[] value();
    }
}

我非常喜欢Checker Framework ,它是类型注释( JSR-308 )的实现,用于实现缺陷检查器(如无效检查器)。我还没有真正尝试过提供任何比较,但是我对此实现感到满意。

我不隶属于提供该软件的组织,但我是粉丝。

我喜欢此系统的四件事:

  1. 它有一个缺陷跳棋NULL 的含量 (@Nullable),但也有药粥不变性实习 (及其他)。我使用第一个(无效),而我试图使用第二个(不变性 / IGJ)。我正在尝试第三个,但是我不确定是否可以长期使用它。我还不相信其他检查器的一般用途,但是很高兴知道框架本身是一个用于实现各种附加注释和检查器的系统。

  2. 无效性检查默认设置运行良好:除本地变量 (NNEL)外,非 null。基本上,这意味着默认情况下,检查器将除本地变量外的所有内容(实例变量,方法参数,泛型类型等)都视为默认情况下的 @NonNull 类型。根据文档:

    NNEL 默认值导致代码中最少数量的显式批注。

    如果 NNEL 不适合您,则可以为类或方法设置不同的默认值。

  3. 通过使用注释将注释括在注释中,例如/*@Nullable*/该框架使您可以在使用框架的情况下使用 with。很好,因为您可以注释和检查库或共享代码,但是仍然可以在不使用框架的另一个项目中使用该库 / 共享代码。这是一个不错的功能。我已经习惯了使用它,即使我现在倾向于在所有项目上启用 Checker 框架。

  4. 该框架提供了一种方法,可以使用存根文件来注释您使用的尚未为空而注释的 API

我使用 IntelliJ,因为我最关心的是 IntelliJ 标记可能产生 NPE 的事物。我同意在 JDK 中没有标准注释会令人沮丧。有人说要添加它,它可能会将其添加到 Java 7 中。在这种情况下,将有更多选择!

根据Java 7 功能列表,将 JSR-308 类型的注释推迟到 Java8。甚至没有提到 JSR-305 注释。

最新的 JSR-308 草案的附录中有关于 JSR-305 的状态的一些信息。这包括观察到 JSR-305 批注似乎已被放弃。 JSR-305 页面还将其显示为 “非活动”。

同时,务实的答案是使用最广泛使用的工具支持的注释类型,并准备在情况发生变化时对其进行更改。


实际上,JSR-308 没有定义任何注释类型 / 类,并且看起来他们认为它不在范围内。 (鉴于 JSR-305 的存在,它们是正确的)。

但是,如果 JSR-308 真正看起来像是要成为 Java 8,那么如果对 JSR-305 的兴趣重新兴起,也不会感到惊讶。 AFAIK,JSR-305 团队尚未正式放弃他们的工作。他们已经安静了 2 年以上。

有趣的是,Bill Pugh(JSR-305 的技术负责人)是 FindBugs 背后的人之一。

对于 Android 项目,您应该使用android.support.annotation.NonNullandroid.support.annotation.Nullable支持库中提供了这些以及其他有用的 Android 专用注释。

http://tools.android.com/tech-docs/support-annotations

支持库本身也已使用这些注释进行注释,因此,作为支持库的用户,Android Studio 将已经检查您的代码并根据这些注释来标记潜在的问题。

<dependency>
    <groupId>org.jetbrains</groupId>
    <artifactId>annotations</artifactId>
    <version>15.0</version>
</dependency>
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;


    /**
     * This annotation can be applied to a package, class or method to indicate that the class fields,
     * method return types and parameters in that element are not null by default unless there is: <ul>
     * <li>An explicit nullness annotation <li>The method overrides a method in a superclass (in which
     * case the annotation of the corresponding parameter in the superclass applies) <li> there is a
     * default parameter annotation applied to a more tightly nested element. </ul>
     * <p/>
     * @see https://stackoverflow.com/a/9256595/14731
     */
    @Documented
    @Nonnull
    @TypeQualifierDefault(
    {
        ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR,
        ElementType.FIELD,
        ElementType.LOCAL_VARIABLE,
        ElementType.METHOD,
        ElementType.PACKAGE,
        ElementType.PARAMETER,
        ElementType.TYPE
    })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NotNullByDefault
    {
    }
@NotNullByDefault
package com.example.foo;
org.eclipse.jdt.annotation.NonNull

只是指出 Java 验证 API( javax.validation.constraints.* )没有附带@Nullable批注,这在静态分析上下文中非常有用。这对于运行时 bean 验证很有意义,因为这是 Java 中任何非原始字段的默认值(即,没有任何要验证 / 强制执行的字段)。出于说明的目的,应权衡其他选择。

// 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);
    }
}
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);
        }