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

1317

我希望让我的代码更易读,并使用工具如IDE代码检查和/或静态代码分析(FindBugs和Sonar)来避免NullPointerException。许多工具似乎与彼此的@NotNull/@NonNull/@Nonnull注释不兼容,在我的代码中列出所有这些注释将非常难以阅读。有哪些推荐的“最佳”注释?以下是我找到的等效注释列表:


258
Apache 应该发明一个“通用”注解,并提供一个工具,可以将其转换为任何其他注解。解决过多标准的问题是发明一个新的标准。 - irreputable
10
如果 Apache 发明了一个新的“通用”工具,可能会有 56 个版本出现,且与其他项目重叠。而且,它也不一定是标准(标准≠普及)。最好使用真正标准的东西,比如 javax?.*。顺便说一下,在这些例子中并没有“太多标准”,我只看到了一个或两个。 - ymajoros
8
javax.annotation.Nonnull 可以与 findbugs 一起使用(我刚测试过),这是我使用它的一个有力理由。 - Nicolas C
26
如果我仅写@NotNull,则指的是com.sun.istack.internal.NotNull。天哪... - Thomas Weller
4
Optionals可以在你以前使用空对象(NullObjects)的情况下非常有用。但它们并没有像运行时的@NotNull注释那样解决相同的问题,而且它们会导致繁琐的解包过程。 - Dave
显示剩余11条评论
25个回答

11

提醒一下,Java验证API (javax.validation.constraints.*) 没有 @Nullable 注解,在静态分析环境中非常有价值。对于运行时的bean验证来说是有意义的,因为在Java中任何非基本类型字段都是默认的(即没有要验证/强制执行的内容)。对于所述目的,这应该考虑使用备选方案。


7

安卓

本回答针对安卓系统。安卓提供了一个支持包 called support-annotations,其中提供了数十个安卓特定的注解,还提供了一些常用的注解,如NonNullNullable等。

要添加support-annotations包,请在您的build.gradle脚本中添加以下依赖项:

compile 'com.android.support:support-annotations:23.1.1'

然后使用:
import android.support.annotation.NonNull;

void foobar(@NonNull Foo bar) {}

7

很遗憾,JSR 308 不会比项目本地的 Not Null 建议提供更多的值。

Java 8 将不会带有单一默认注释或自己的 Checker 框架。 与 Find-bugs 或 JSR 305 类似,这个 JSR 被一个大多数是学术团队的少数人糟糕地维护着。

没有商业实力支持,因此 JSR 308 现在启动了 JCP 的 EDR3(早期草案审查),而 Java 8 预计将在不到 6 个月的时间内发布:-O 类似于 310,但不同于 308 Oracle,它已经从其创始人手中接管,以最小化对 Java 平台造成的损害。

每个项目、厂商和学术课程,如 Checker FrameworkJSR 308 背后的课程,都将创建其自己的专有的检查器注释。

使得源代码在未来几年内不兼容,直到找到一些受欢迎的妥协并可能添加到 Java 910,或通过像 Apache CommonsGoogle Guava 这样的框架进行添加;-)


7
这里已经有太多的答案了,但是(a)现在是2019年,还没有“标准”的Nullable,(b)其它答案也没有提到Kotlin。
提到Kotlin很重要,因为Kotlin与Java完全兼容,并且具有核心空安全功能。在调用Java库时,它可以利用这些注释来让Kotlin工具知道Java API是否可以接受或返回null。
据我所知,与Kotlin兼容的唯一Nullable包是org.jetbrains.annotations和android.support.annotation(现在是androidx.annotation)。后者仅与Android兼容,因此不能在非Android JVM/Java/Kotlin项目中使用。但是,JetBrains包可以在任何地方使用。
因此,如果您开发的Java包也应该在Android和Kotlin中工作(并且受到Android Studio和IntelliJ的支持),最好选择JetBrains包。
Maven:
<dependency>
    <groupId>org.jetbrains</groupId>
    <artifactId>annotations-java5</artifactId>
    <version>15.0</version>
</dependency>

Gradle:

implementation 'org.jetbrains:annotations-java5:15.0'

4
嗯,这个链接说的不一样:https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations - skagedal
具体而言,Kotlin还记录了对javax.annotation、edu.umd.cs.findbugs.annotations、org.eclipse.jdt.annotation和lombok.NonNull的支持,以及实现也包括org.checkerframework和io.reactivex.annotations。 - Joe

6
在Java 8中还有另一种方法来实现这个目标。我需要完成两件事情:
1.使用java.util.Optional将可为空的字段包装起来,以便在类型中明确可为空的字段。
2.使用java.util.Objects.requireNonNull检查所有非可为空的字段在构造时是否为null。
示例:请忽略此第一个示例,它只是作为评论对话的上下文而保留。跳到推荐选项后(第二个代码块)。
    import static java.util.Objects.requireNonNull;

    public class Role {

      private final UUID guid;
      private final String domain;
      private final String name;
      private final Optional<String> description;

      public Role(UUID guid, String domain, String name, Optional<String> description) {
        this.guid = requireNonNull(guid);
        this.domain = requireNonNull(domain);
        this.name = requireNonNull(name);
        this.description = requireNonNull(description);
      }
   }

所以我的问题是,使用Java 8时,我们是否需要注释?
编辑:后来我发现,在参数中使用Optional被认为是一种不好的做法。在这里,有一个讨论其利弊的好文章:为什么不应该在Java 8中使用Optional作为参数 建议选项是,在参数中使用Optional不是最佳实践,因此我们需要两个构造函数。
public class Role {
      
      // Required fields, will not be null (unless using reflection) 
      private final UUID guid;
      private final String domain;
      private final String name;
      // Optional field, not null but can be empty
      private final Optional<String> description;

  //Non null description
  public Role(UUID guid, String domain, String name, String description) {
        this.guid = requireNonNull(guid);
        this.domain = requireNonNull(domain);
        this.name = requireNonNull(name);

        // description will never be null
        requireNonNull(description);

        // but wrapped with an Optional
        this.description = Optional.of(description);
      }

  // Null description is assigned to Optional.empty
  public Role(UUID guid, String domain, String name) {
        this.guid = requireNonNull(guid);
        this.domain = requireNonNull(domain);
        this.name = requireNonNull(name);
        this.description = Optional.empty();
      }
  //Note that this accessor is not a getter.
  //I decided not to use the "get" suffix to distinguish from "normal" getters 
  public Optional<String> description(){ return description;} 
 }

1
我认为你仍然需要为所有4个形式参数添加@NotNull注释,以便静态分析检查器知道你的意图是不允许为空。目前Java语言中还没有强制执行这一点的内容。如果你正在进行防御性编程,你还应该检查描述是否为空。 - jaxzin
4
我仍然可以编写这段代码:new Role(null,null,null,null);。使用我的集成开发环境和静态分析工具的注释,它们会提示null不能被传递进这些参数中。如果没有注释,我只有在运行代码时才能发现这个问题。这就是注释的价值。 - jaxzin
2
我也在开发环境中,开发人员可以使用他们喜欢的任何IDE或文本编辑器,这并不是互斥的。然后,我们还将maven-pmd-plugin和/或SonarQube集成到构建过程中,以鼓励、突出甚至限制代码质量问题的合并前检查,例如在拉取请求上。 - jaxzin
3
Optional 不应当被用作方法参数或私有字段。例如请参考:https://stuartmarks.wordpress.com/2016/09/27/vjug24-session-on-optional/ - assylias
1
@assylias 是的,我后来发现了,他们说这样做并不推荐,因为它不会给我们带来任何好处,我完全理解他们的理性。在这种情况下,有人可能会认为description不能为空,客户端代码可以传递一个空字符串,但在许多情况下,区分空字符串和没有值可能非常方便。感谢您的评论。我会更新答案。 - Mozart Brocchini
显示剩余6条评论

6

较新的项目应该使用jakarta.annotation-apijakarta.annotation包)。
它现在链接到只读的javax.annotation repo,并适配到旨在解决所有与javax相关问题的新的jakarta生态系统中。


3
Jakarta注解API是未来:jakarta.annotation.Nonnulljakarta.annotation.Nullable。它们是标准的,并具有通用语义。再见Javax验证API注释! - gavenkoa

6
如果您正在处理一个大型项目,最好创建自己的@Nullable和/或@NotNull注释。例如:
@java.lang.annotation.Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)
@java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD,
                              java.lang.annotation.ElementType.METHOD,    
                              java.lang.annotation.ElementType.PARAMETER,
                              java.lang.annotation.ElementType.LOCAL_VARIABLE})
public @interface Nullable 
{
}

如果使用正确的保留策略,注释在运行时不可用。从这个角度来看,它只是一个内部的东西。
尽管这不是一门严格的科学,但我认为最好使用一个内部类。
  • 这是一个内部的东西。(没有功能或技术影响)
  • 有很多很多用途。
  • 像IntelliJ这样的IDE支持自定义的@Nullable/@NotNull注释。
  • 大多数框架也更喜欢使用它们自己的内部版本。

附加问题(请参见评论):

如何在IntelliJ中配置此项?

单击IntelliJ状态栏右下角的“警察”,然后单击弹出窗口中的“配置检查”。接下来... configure annotations


1
我尝试了你的建议,但是 idea 对于 void test(@NonNull String s) {}test(null); 调用并没有发出警告。 - user1244932
3
您是指 IntelliJ IDEA 吗?您可以配置用于静态分析的 nullability 注释。我不确定具体在哪里,但是其中一个定义它们的位置是在“文件 > 设置 > 构建、执行、部署 > 编译器”中,然后有一个名为“配置注释...”的按钮。 - Adowrath
如果您仍在寻找此内容,请查看屏幕截图,@user1244932。 - bvdb

5

在等待上游(Java 8?)解决此问题的同时,您也可以定义自己的项目本地 @NotNull@Nullable 注释。这在使用 Java SE 的情况下也很有用,因为默认情况下不支持 javax.validation.constraints,可以参考此处

import java.lang.annotation.*;

/**
 * Designates that a field, return value, argument, or variable is
 * guaranteed to be non-null.
 */
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@Documented
@Retention(RetentionPolicy.CLASS)
public @interface NotNull {}

/**
 * Designates that a field, return value, argument, or variable may be null.
 */
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@Documented
@Retention(RetentionPolicy.CLASS)
public @interface Nullable {}

这项工作主要是为了装饰或未来的需求,因为以上内容本身并没有对这些注释的静态分析提供任何支持。


5
在IntelliJ中的一个好处是你不需要使用他们的注解。你可以编写自己的注解,或者使用任何其他工具的注解。甚至不限于一种类型。如果你正在使用两个使用不同 @NotNull 注解的库,你可以告诉 IntelliJ 使用它们两个。要做到这一点,请进入“配置检查”,点击“常量条件和异常”的检查,并点击“配置检查”按钮。我在任何地方都使用Nullness Checker,所以我设置了IntelliJ来使用这些注解,但是你可以使它与任何其他工具一起使用(因为我已经使用IntelliJ的检查很多年了,我深爱着它们)。

5

如果您正在使用Spring框架构建应用程序,我建议使用来自Beans验证javax.validation.constraints.NotNull,它包含在以下依赖项中:

    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.1.0.Final</version>
    </dependency>

这个注解的主要优点是,Spring 框架支持使用 javax.validation.constraints.NotNull 注解标记方法参数和类字段。要启用支持,只需执行以下操作:
  1. supply the api jar for beans validation and jar with implementation of validator of jsr-303/jsr-349 annotations (which comes with Hibernate Validator 5.x dependency):

    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.1.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.1.Final</version>
    </dependency>
    
  2. provide MethodValidationPostProcessor to spring's context

      @Configuration
      @ValidationConfig
      public class ValidationConfig implements MyService {
    
            @Bean
            public MethodValidationPostProcessor providePostProcessor() {
                  return new MethodValidationPostProcessor()
            }
      }
    
  3. finally you annotate your classes with Spring's org.springframework.validation.annotation.Validated and validation will be automatically handled by Spring.

例子:

@Service
@Validated
public class MyServiceImpl implements MyService {

  @Override
  public Something doSomething(@NotNull String myParameter) {
        // No need to do something like assert myParameter != null  
  }
}

当你尝试调用doSomething方法并将参数值设置为null时,Spring(通过HibernateValidator)会抛出ConstraintViolationException异常。这里不需要手动操作。
您还可以验证返回值。
javax.validation.constraints.NotNull的另一个重要优点是它仍在开发中,并且计划为新版本2.0添加新功能。
那@Nullable呢?Beans Validation 1.1中没有类似的内容。好吧,我可以说如果你决定使用@NotNull,那么所有未注释@NonNull的内容实际上都是“可空”的,因此@Nullable注释是无用的。

2
请不要使用它。它用于运行时验证,而不是静态代码分析。有关详细信息,请参见http://justsomejavaguy.blogspot.com/2011/08/nullable-null-notnull-notnull-nonnull.html。来源:@luis.espinal的已删除答案,获得219票。 - koppor
@koppor:我不同意。如果这不是用于使用,为什么Spring会在运行时处理它呢?此外,Beans验证框架允许创建纯用于运行时分析的注释,因为它允许在运行时访问上下文对象(当前被注释/验证的实例)。 - walkeros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接