我为什么要使用Lombok的@NonNull注解?

20

Lombok提供注解@NonNull,它执行nullcheck并抛出NPE(如果未进行不同配置)。

我不明白为什么要使用该注释,如文档中所述的示例:

private String name;
public NonNullExample(@NonNull Person person) {
    super("Hello");
    if (person == null) {
      throw new NullPointerException("person is marked @NonNull but is null");
    }
    this.name = person.getName();
  }

NPE(NullPointerException)无论如何都会被抛出。在我看来,使用注释的唯一原因是如果您希望异常与NPE不同。编辑:我知道异常会被显式地抛出,因此“受控”,但至少错误消息的文本应该是可编辑的,不是吗?

1
考虑一个例子。您正在设计API,并将字段标记为“@NonNull”。每当相应的JSON到达后端API时,它会在控制器级别抛出NPE,而不是通过代码进行处理,这也被称为“fail-fast”,其他事情取决于用例。 - bananas
3
我认为这个例子的目的是展示如果你编写 @NonNull 注解,会生成什么代码(因此有“Vanilla Java”标题)。请注意,不要改变原文的意思,尽可能使翻译通俗易懂。 - Jos
@XavierBouclet,我可能错了,我在Java方面经验不太丰富,但我认为它们只是服务于不同的目的。 - Sasha
1
看起来你是对的。我以为它们是一样的。 - Xavier Bouclet
1
@Sasha lombok总是除了@NonNull之外都会剥离它们。原因是:nonnull是唯一一个用于文档/ lint工具目的的注释。所有其他lombok注释都不需要,因此在lombok完成其工作后可以将其删除。 - rzwitserloot
显示剩余2条评论
5个回答

16

编写类似@NonNull的类型注解有几个作用。

  • 它是文档:以比Javadoc文本更简洁和精确的方式向客户端传达方法的契约。
  • 它启用了运行时检查 -- 也就是说,如果有错误的客户端错误使用您的方法,它保证程序会崩溃并提供有用的错误消息(而不是做更糟糕的事情)。 Lombok可以为您完成此操作,而无需强制程序员编写运行时检查。 引用的示例 显示了两种方法:使用单个@NonNull注解或使用显式编写的程序员检查。 "Vanilla Java"版本可能有一个错别字(一个迷失的@NonNull)或显示经过Lombok处理后的代码。
  • 它启用了编译时检查。像Checker Framework这样的工具能够保证代码在运行时不会崩溃。诸如NullAwayError ProneFindBugs之类的工具是启发式错误查找器,会警告您有关空值的一些误用,但不会给您任何保证。

6
如果能够告诉lombok默认情况下所有内容都是NonNull,只需要注释可为空的参数而不是相反的方式,那就太好了。 - Klesun

3
在我看来,你对文档页面的理解是错误的。
那个文档页面并没有暗示你应该同时使用Lombok的@NonNull注解和显式的if (smth == null) throw…-like检查(在同一个方法中)。
它只是说像这样的代码(让我们称之为代码A):
import lombok.NonNull;

public class NonNullExample extends Something {
  private String name;

  public NonNullExample(@NonNull Person person) {
    super("Hello");
    this.name = person.getName();
  }
}

将会由Lombok自动(内部)翻译成像问题中引用的代码B一样的代码(我们称之为代码B)。

但是该文档页面并没有说你明确地编写代码B是有意义的(尽管你被允许这样做;而且Lombok甚至会尝试防止在这种情况下进行双重检查)。它只是说使用Lombok您现在可以编写代码A(以及它如何工作——它将隐式转换为代码B)。

请注意,代码B是“普通Java”代码。不希望对其进行第二次处理。因此,在代码B中的@NonNull只是一个普通的注释,它对行为没有影响(至少不是通过Lombok手段)。

这是一个单独的问题,为什么 Lombok 以那种方式工作——为什么它不会从生成的代码中删除 @NonNull。最初我甚至认为这可能是文档页面上的错误。但是,正如 Lombok 的作者在他的评论中解释的那样,@NonNull 被有意保留用于文档和其他工具的处理。

虽然你说的不错,但仍然有可能在没有使用它们的注解处理器的情况下使用lombok注释。同样有可能他们的示例之所以将参数注释为@NonNull纯粹是为了文档目的。 - BeUndead
1
@user2478398,在其他文档页面中他们不这样做。 - Sasha
4
Lombok的作者在这里:不,这并不是虚假的。之所以“post lombok”示例仍然具有nonnull注释,是因为与大多数其他Lombok注释不同,如果您将此代码delombok,则我们会保留该注释。这是因为正如@mernst所说,“@NonNull”注释还用作文档和“输入linter”的目的,而大多数其他Lombok注释没有这样做。该示例显示了Lombok将生成一个nullcheck,这就是它所做的全部。 - rzwitserloot

3
我喜欢lombok,但在这种情况下(个人而言),我更喜欢使用javax.annotation中的@Nonnull注释和java.util.Objects中的Objects.requireNonNull。使用lombok可以使代码更清晰,但也更不清晰和可读:
public Builder platform(@NonNull String platform) {
    this.platform = platform;
    return this;
}  

这个方法会引发空指针异常(没有证据),并且在方法调用中传递空参数,我的IDE(IntelliJ Ultimate 2020.1 EAP - 最新版本 - 带有lombok插件)不会报告。

因此,我更喜欢以这种方式使用来自javax.annotation@Nonnull注释:

public Builder platform(@Nonnull String platform) {
    this.platform = Objects.requireNonNull(platform);
    return this;
}

代码有点啰嗦,但更清晰,我的IDE能够警告我在方法调用时传递了空参数!

1
你可以配置Lombok使用Objects.requireNonNull或Guava。 - Ahmed

2
注释的思想是避免在您的代码中出现if (person == null),从而使您的代码更加简洁。

4
如果代码中未添加检查,则无论如何都会引发 NPE。 - metters
2
是的,如果你在这里处理 person.xxxx,但你可以将其传递给一个函数,并在后面使用它。通过注释,你可以在开始时检查代码,并保持代码整洁。 - Roee Gavirel
@RoeeGavirel,在执行任何person.xxx或将person传递给函数之前,会执行if (person == null) throw new NullPointerException("person is marked @NonNull but is null") - Sasha
@Sashe - 是的,但是使用@NonNull可以使if (person == null)变得多余。 - Roee Gavirel
@RoeeGavirel,没错 - 这就是metters的问题所在(如果我们保留if (person == null),为什么要添加@NonNull)。 - Sasha

0

它的作用类似于

java.util.Objects requireNonNull()

或者Guava的PreConditions。这只是使代码更紧凑和快速失败。


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