协慌网

登录 贡献 社区

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

我希望使我的代码更具可读性,并使用 IDE 代码检查和 / 或静态代码分析(FindBugs 和 Sonar)等工具来避免 NullPointerExceptions。许多工具似乎与彼此的@NotNull / @NonNull / @Nonnull注释不兼容,并且在我的代码中列出所有这些工具都很难阅读。有什么建议是 “最好的” 吗?这是我发现的等效注释列表:

  • javax.validation.constraints.NotNull
    创建用于运行时验证,而不是静态分析。
    文件

  • edu.umd.cs.findbugs.annotations.NonNull
    Findbugs静态分析使用,因此声纳(现为Sonarqube
    文件

  • javax.annotation.Nonnull
    这可能也适用于 Findbugs,但JSR-305处于非活动状态。 (另请参阅: JSR 305 的状态是什么? 来源

  • org.jetbrains.annotations.NotNull
    由 IntelliJ IDEA IDE 用于静态分析。
    文件

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

  • android.support.annotation.NonNull
    Android 中提供的标记注释,由 support-annotations 包提供
    文件

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

答案

由于甲骨文目前决定不对 @NonNull(和 @Nullable)进行标准化 ,我担心没有好的答案。我们所能做的就是找到一个实用的解决方案,我的如下:

句法

从纯粹的风格角度来看,我想避免任何对 IDE,框架或除 Java 本身之外的任何工具包的引用。

这排除了:

  • android.support.annotation
  • edu.umd.cs.findbugs.annotations
  • org.eclipse.jdt.annotation
  • org.jetbrains.annotations
  • org.checkerframework.checker.nullness.qual
  • lombok.NonNull

这给我们留下了 javax.validation.constraints 或 javax.annotation。前者配有 JEE。如果这比 javax.annotation 更好,这可能最终会出现在 JSE 中,或者从来没有出现过,这是一个有争议的问题。我个人更喜欢 javax.annotation,因为我不喜欢 JEE 依赖。

这让我们失望了

javax.annotation 中

这也是最短的一个。

只有一种语法更好:java.annotation.Nullable。由于过去其他软件包从 javax 毕业到 java,javax.annotation 将是朝着正确方向迈出的一步。

履行

我希望它们都具有基本相同的简单实现,但详细的分析表明这不是真的。

首先是相似之处:

@NonNull 注释都有这一行

public @interface NonNull {}

除了

  • org.jetbrains.annotations 将其称为 @NotNull,并且具有简单的实现
  • javax.annotation 具有更长的实现
  • javax.validation.constraints 也称它为 @NotNull 并具有实现

@Nullable 注释都有这一行

public @interface Nullable {}

除了(再次)org.jetbrains.annotations 及其简单的实现。

对于差异:

一个引人注目的是

  • javax.annotation 中
  • javax.validation.constraints
  • org.checkerframework.checker.nullness.qual

都有运行时注释(@Retention(RUNTIME),而

  • android.support.annotation
  • edu.umd.cs.findbugs.annotations
  • org.eclipse.jdt.annotation
  • org.jetbrains.annotations

只是编译时间(@Retention(CLASS))。

正如本回答中所述,运行时注释的影响比人们想象的要小,但除了编译时间之外,它们还有使工具能够进行运行时检查的好处。

另一个重要的区别是代码可以使用注释。有两种不同的方法。某些包使用 JLS 9.6.4.1 样式上下文。下表给出了概述:

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

org.eclipse.jdt.annotation,javax.annotation 和 org.checkerframework.checker.nullness.qual 使用 JLS 4.11 中定义的上下文,我认为这是正确的方法。

这让我们失望了

  • javax.annotation 中
  • org.checkerframework.checker.nullness.qual

在这一轮。

为了帮助您自己比较更多详细信息,我列出了下面每个注释的代码。为了便于比较,我删除了注释,导入和 @Documented 注释。 (他们都有 @Documented,但 Android 包中的类除外)。我重新排序了行和 @Target 字段并对资格进行了规范化。

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 {}

为了完整性,这里是 @Nullable 实现:

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 {}

以下两个包没有 @Nullable,所以我单独列出它们 lombok 有一个非常无聊的 @NonNull。在 javax.validation.constraints 中,@ NonNull 实际上是 @NotNull,它有一个很长的实现。

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

支持

从我的经验来看,javax.annotation 至少得到 Eclipse 和 Checker Framework 的支持。

摘要

我理想的注释是带有 Checker Framework 实现的 java.annotation 语法。

如果您不打算使用 Checker Framework,那么javax.annotationJSR-305 )仍然是您最好的选择。

如果您愿意购买 Checker Framework,只需使用他们的 org.checkerframework.checker.nullness.qual。


来源

  • android-5.1.1_r1.jar 中的 android.support.annotation
  • 来自 findbugs-annotations-1.0.0.jar 的 edu.umd.cs.findbugs.annotations
  • 来自 org.eclipse.jdt.annotation_2.1.0.v20160418-1457.jar 的 org.eclipse.jdt.annotation
  • 来自 jetbrains-annotations-13.0.jar 的 org.jetbrains.annotations
  • 来自 gwt-dev-2.5.1-sources.jar 的 javax.annotation
  • 来自 checker-framework-2.1.9.zip 的 org.checkerframework.checker.nullness.qual
  • 来自 lombok 的 lombok 提交 f6da35e4c4f3305ecd1b415e2ab1b9ef8a9120b4
  • 来自 validation-api-1.0.0.GA-sources.jar 的 javax.validation.constraints

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

我不是提供该软件的团体的附属机构,但我是粉丝。

我喜欢这个系统的四件事:

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

  2. nullness 检查默认设置很有效:除了本地(NNEL)之外的非 null。基本上这意味着默认情况下,检查器会处理除局部变量之外的每个(实例变量,方法参数,泛型类型等),就像它们默认具有 @NonNull 类型一样。根据文件:

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

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

  3. 通过将注释括在注释中,此框架允许您使用而不创建对框架的依赖 :例如/*@Nullable*/ 。这很好,因为您可以注释和检查库或共享代码,但仍然能够在不使用框架的另一个项目中使用该库 / 共享编码。这是一个很好的功能。我已经习惯使用它,即使我现在倾向于在我的所有项目中启用 Checker Framework。

  4. 该框架有一种方法可以使用存根文件来注释您使用的尚未注释为 null 的API

我使用 IntelliJ,因为我主要关注 IntelliJ 标记可能产生 NPE 的东西。我同意在 JDK 中没有标准注释令人沮丧。有关添加它的讨论,它可能会成为 Java 7. 在这种情况下,还有一个可供选择!