我应该一起使用@NotNull和@NonNull吗?

时间:2020-02-04 21:48:34

标签: java spring-boot lombok

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

1 个答案:

答案 0 :(得分:7)

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

类型系统解释(“不能!”)

此解释是一种类型断言,它试图与String中的String x;一样强:对于该x变量,它不可能引用一个不是字符串的对象,并且由于不可能,自然地,对此进行检查将完全没有用。有了该声明,实际上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中的红色波浪下划线都是如此,因此写{{1 }},其中someMethod的参数使用这些someMethod(map.get(key))注释之一进行注释。与其说“你不应该”,不如说是“你不能那样做;我什至不让你”。

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

当然,这很复杂:要绕开一个事实,那就是Java编译器是否实际上会阻止您打破这些非null注释的含义……某些工具(例如kotlinc)总会注入显式nullcheck。有点像泛型如何导致javac在您从未编写过显式强制转换的地方注入一些类型检查,以解决泛型是在编译时附加以保持向后兼容性的事实。想法是考虑这些您不应该考虑的实现细节。

数据库解释

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

这与类型系统的解释如枪和奶奶一样:您可以具有表示数据库中行的对象,但是由于始终违反约束而无法成功保存;例如,任何自动计数的unid字段通常为0,并且不会像这样保存(数据库引擎将0升级为“没关系,请插入;没有它插入并让数据库引擎的序列填充此数字)”。这些就是这样:对于Java来说,这根本没有任何意义。 SQL引擎将负责指示由于约束失败而导致插入/更新失败。当然,为了节省往返数据库的时间,并且因为不允许这样做很简单,所以大多数数据库框架都会在调用CREATE TABLE.save()的等效项时进行nullcheck。但这只是该数据库约束的捷径。

验证解释

有时Java中的对象代表外部事物。例如数据库行(在某种程度上与先前的含义重叠),或者说是用户提交的Web表单。这些对象应该精确地表示它所代表的外部事物的实际状态。疣和无效的鸡毛草等等。

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

龙目岛解释

不幸的是,就像所有其他框架一样,龙目岛使水有些混乱。龙目岛.store()的含义取决于其出现的位置。如果在参数上,则表示:Lombok,请生成一个显式的nullcheck作为方法的第一行,除非我自己写了它。

在字段上,它的意思是:“出于@NonNull的考虑,此字段必填;在生成的构造函数中对它进行nullcheck(抛出异常)。此外,在相关时复制null注释,因此,如果为此字段设置了一个设置器。在内部添加该nullcheck。”

您的具体情况

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

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

甚至没有一丝曙光。对于真正的大脑扭曲者,请考虑checkerframework's @PolyNull,并认为这是对正确的类型系统应该真正能够处理的东西的过度简化!

免责声明:我是Lombok项目的核心撰稿人。

相关问题