【问题标题】:Should I use @NotNull and @NonNull together?我应该一起使用@NotNull 和@NonNull 吗?
【发布时间】:2020-05-20 18:57:49
【问题描述】:

我正在使用 javax.validation.constraints.NotNull 进行休眠检查空字段。现在我正在使用 lombok 来简化构造函数代码,但我需要使用 @NonNull 注解使字段包含在必填字段构造函数中。我应该一起使用它们吗?

【问题讨论】:

  • 如果你使用@RequiredArgsConstructor,所有用@NotNull注解的字段都应该在生成的构造函数中自动得到一个空检查。所以我不太明白你的问题。
  • @JanRieke 我猜,你的意思是@lombok.NonNull,因为其他no[nt][Nn]ullannotations AFAIK 不会导致空检查。 +++ Hibernate 可以使用设置器或直接字段访问(即使对于私有字段)。在后一种情况下,lombok 什么也做不了,这可能就是@javax.validation.constraints.NotNull 的帮助所在。
  • @JanRieke,使用 javax.validation.constraints.NotNull 注释的字段未显示在@RequiredArgsConstructor

标签: java spring-boot lombok


【解决方案1】:

非空注释的作用至少有 4 个完全不同的含义。大多数注释仅暗示这些不同事物之一。因此,是的,应用多个这样的注释是有意义的。不幸的是,这些注释的实际含义非常混乱,因为根据我的经验,几乎没有开发人员考虑过它们的含义不同的事实。事实上,我遇到的大多数开发人员都完全没有意识到 nullity annotations 非常复杂且定义模糊的事实。

类型系统解释('cannot be!')

这种解释是一种类型断言,它试图与String x; 中的String 一样强:对于x 变量,它不可能引用一个不' t 一个字符串,因为不可能,所以自然地,检查它是一个完全没用的想法。鉴于该声明,String z = (String) x 之类的东西是如此无用,事实上它是一个编译器警告。

这样的注释也是如此。这方面的例子是 checkerframework、eclipse 和 intellij 的:org.checkerframeworkchecker.nullness.qual.NonNullorg.jetbrains.annotations.NotNull 和 eclipse 的 org.eclipse.jdt.annotation.NonNull 都主要暗示这个意思(尽管 intellij 一个适用于参数和方法,而 checkerframework 和 eclipse 是 type_use 注释。我告诉过你这很复杂 :P)。

这些注释,通过暗示“不能”,用于写入时检查。出于同样的原因,写String x = 5; 是一个立即,当你写代码时,甚至在你保存文件之前,你的IDE 中的红色波浪下划线,写someMethod(map.get(key)) 也是如此,其中someMethod 的参数使用这些NonNull 注释之一进行注释。与其说是“你不应该”,不如说是“你不能那样做;我什至不会让你”。

当然,javac 编译器实际上并不是这样工作的,而是 IDE 和工具试图让它看起来像那样。就像您永远不会将 String 类型的变量转换为 String 一样,您实际上并不认为此类无效注释暗示您应该检查。不;意思是:不是。这意味着检查已经完成,您无需再次检查。

当然,这很复杂:要绕开 java 编译器实际上是否会阻止您破坏这些非空注释的含义这一事实跳舞……一些工具(例如 kotlinc)无论如何都会注入显式的空检查。有点像泛型如何导致 javac 在您从未编写过显式转换的地方注入一些类型检查,以解决泛型是编译时附加组件以保持向后兼容性的事实。我们的想法是考虑这些你不应该考虑的实现细节。

数据库解释

当 hibernate 使用类作为模板来生成CREATE TABLE SQL 语句时,能够使用注释来设置约束和规则等是很有用的。出于同样的原因,您可能希望将字段标记为:“将其转换为 SQL 列时,告诉数据库引擎为此放置唯一索引”,您可能希望将其标记为:“将其转换为 SQL 时列,告诉数据库引擎对其施加非空约束”。

这与类型系统解释相关,就像枪支和祖母一样:您可以拥有表示数据库中行但由于打破约束而无法成功保存的对象一直;例如,任何自动计数的unid字段通常为0,并且不会那样保存(数据库引擎将该0升级为“没关系;插入没有它,让数据库引擎的序列填充这个数字)。这些也是如此:它对 java 根本没有任何意义; SQL 引擎将负责指示插入/更新由于失败的约束而失败。当然,为了节省到数据库的往返行程,并且因为这不允许简单,大多数数据库框架将在您调用.save().store() 的等效项时进行空检查。但这只是该数据库约束的捷径。

验证解释

有时java中的对象代表一个外部事物。例如 DB 行(与前面的含义有些重叠),或者说,用户提交的 Web 表单。这样的对象应该精确地代表它所代表的实际外部事物的实际状态。疣和无效的 gobbledygook 等等。

通常你有一个框架可以让你验证这些。这就是validation nonnull 的含义:该字段可以为null,如果您尝试将其设置为null,则不会发生异常(因此,它们可以是)。但是,只要该字段保持为空,如果您问我该对象是否有效,答案是:否。

龙目岛解释

不幸的是,lombok 有点搅浑水......就像所有其他框架一样。 lombok 的@NonNull 的含义取决于它出现的位置。如果在参数上,则表示:Lombok,请在方法的第一行生成一个显式的 nullcheck,除非我已经自己编写了。

在字段上,它的意思是:“为了@RequiredArgsConstructor,考虑这个字段是必需的;在生成的构造函数中对其进行空检查(抛出异常)。此外,在相关的地方复制空值注释,因此,如果设置器是为这个字段制作的。在里面添加那个 nullcheck。"

您的具体情况

鉴于 nullity annotations 意味着完全不同的东西,因此将多个这样的注释应用于代码中的同一构造可能并不疯狂。如果您希望数据库引擎在此类用作模板的情况下生成 SQL NON NULL 约束,并且对于表示此表中的行的任何对象立即拒绝任何将其置于无效状态的尝试,添加两者都是有意义的。如果您希望对象表示无效状态是可以接受的,如果一般原则是构造一个空白对象然后使用 setter 一次设置每个字段值,则几乎总是这种情况——那么不要添加 lombok 的注释。

您肯定已经了解了所有复杂性,对吧?

连一丝微光都没有。对于真正的脑筋急转弯,请考虑checkerframework's @PolyNull,并认为这是对正确类型系统应该真正能够处理的内容的过度简化!

免责声明:我是 Project Lombok 的核心贡献者。

【讨论】:

  • 哦,PolyNull,这就像泛型而不是空值,什么的。
猜你喜欢
  • 2018-06-10
  • 2011-06-25
  • 1970-01-01
  • 2016-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-13
  • 1970-01-01
相关资源
最近更新 更多