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

3,496 阅读4分钟

我应该使用哪个@NotNull Java注释?| Java Debug 笔记

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看<活动链接>

提问:

我希望使我的代码更具可读性,并使用诸如IDE代码检查和 / 或静态代码分析(FindBugs和Sonar——这两个是检查代码规范的工具集,译者注)之类的工具来避免NullPointerExceptions。许多工具似乎彼此互不兼容,在我的代码中列出了这些工具里的@NotNull / @NonNull / @Nonnull注释,所有这些都很难读。你们有没有最佳的建议?这是我发现的等效注释的列表:

回答:

由于JSR 305(其目标是使@NonNull@Nullable标准化)已经休眠了几年,所以恐怕没有好的答案。我们所能做的就是找到一个务实的解决方案,我的方法如下:

句法

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

这排除了:

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

剩下的就是javax.validation.constraintsjavax.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并具有实现

    @Nullableannotations都有一行

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.annotationorg.checkerframework.checker.nullness.qua使用JLS 4.11中定义的上下文,我认为这是正确的方法。

这给我们留下了

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

代码示例

为了帮助您自己比较更多详细信息,我在下面列出了每个注释的代码。为了使比较容易,我删除了注释,导入和@Documented批注。 (除了Android包中的类之外,其他所有文件都具有@Documented)。我对这些代码行和@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,因此我将它们分开列出;Lombox有一个很少用的@NonNul。在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();
    }
}

支持

根据我的经验,Eclipse和Checker Framework至少是开箱即用地支持javax.annotation

总结

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

如果您不打算使用Checker Framework,则暂时最好还是使用javax.annotationJSR-305)。

如果您愿意尝试Checker Framework,请使用其org.checkerframework.checker.nullness.qual

用到的源码

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

原文链接