我应该同时使用@NotNull和@NonNull吗?

5

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


如果您使用@RequiredArgsConstructor,那么所有用@NotNull注释的字段应该在生成的构造函数中自动进行空值检查。所以我真的不明白你的问题。 - Jan Rieke
@JanRieke 我猜你的意思是 @lombok.NonNull,因为其他的 no[nt][Nn]ull 注解据我所知并不会导致空指针检查。+++ Hibernate 可能会使用 setter 或直接字段访问(即使是私有字段)。在后一种情况下,lombok 无法做任何事情,这可能就是 @javax.validation.constraints.NotNull 有用的地方。 - maaartinus
@JanRieke,使用javax.validation.constraints.NotNull注释的字段不会显示在@RequiredArgsConstructor中。 - PLee
1个回答

14
至少有4个完全不同的含义可以用nonnull注释来表达。大多数注释只意味着其中一个不同的含义。因此,是的,应用多个这样的注释是有意义的。不幸的是,这些注释的实际含义非常模糊,因为根据我的经验,几乎没有开发人员考虑过它们意味着不同的事情;事实上,我遇到的大多数开发人员都完全不知道nullity注释是非常复杂和含糊定义的。
类型系统解释(“不能是!”):
这种解释是一种类型断言,试图像String x;中的String一样强:这个x变量引用的对象不可能不是字符串,因为这是不可能的,自然而然地,检查这个是完全无用的想法。在给定的声明中,像String z = (String) x这样的东西是如此无用,以至于它是编译器警告。
因此,这种注释也是如此。checkerframework、eclipse和intellij的org.checkerframework.checker.nullness.qual.NonNullorg.jetbrains.annotations.NotNull和eclipse的org.eclipse.jdt.annotation.NonNull主要都暗示这个含义(尽管intellij适用于参数和方法,而checkerframework和eclipse是类型使用注释。我告诉过你这很复杂:P)。
这些注释是用于编写时检查的,通过暗示“不可能”。出于同样的原因,编写String x = 5;是一个立即的操作,当您编写该代码并在保存文件之前,IDE中会出现红色波浪线,同样的道理适用于编写someMethod(map.get(key)),其中someMethod的参数带有这些NonNull注释之一。它不是“你不应该这样做”,而是“你不能这样做;我甚至不会让你这样做”。
当然,javac编译器实际上并不是这样工作的,是IDE和工具试图使其看起来像那样。就像您永远不会将类型为String的变量强制转换为String一样,您实际上不认为这种空值注释意味着您应该进行检查。不;它的意思是检查已经完成,您不需要再次检查。
当然,这很复杂:为了绕过Java编译器是否实际上阻止您打破这些nonnull注释的含义这一事实......一些工具(如kotlinc)将注入显式的nullchecks。有点像泛型可以导致javac在您从未编写显式转换的地方注入一些类型检查,以解决泛型是编译时附加的问题以保留向后兼容性的事实。这个想法是考虑这些您不应该思考的实现细节。

数据库解释

当Hibernate使用一个类作为模板来生成一个CREATE TABLE SQL语句时,使用注释设置约束和规则等是很有用的。由于同样的原因,你可能想将一个字段标记为:“在将其转换为SQL列时,告诉DB引擎在其上放置唯一索引”,你可能想将其标记为:“在将其转换为SQL列时,告诉DB引擎对其施加非空约束”。
这与类型系统解释有关,就像枪支和祖母一样:你可以拥有代表DB中行的对象,但由于总是违反约束而无法成功保存;例如,任何自动计数的单向字段通常为0,它不会像这样保存(DB引擎将该0升级为“不要紧;插入时不考虑它,让DB引擎的序列填充这个数字)。所以对于这些:Java实际上根本没有任何意义;SQL引擎将负责指示插入/更新失败,因为约束失败。当然,为了节省与DB的往返时间,因为这只是不能简单处理,大多数DB框架将在调用相当于.save().store()的函数时进行null检查。但这只是一个快捷方式到DB约束。
验证解释
有时,Java中的对象表示外部事物。例如,DB行(在某种程度上与前面的含义重叠),或者,由用户提交的Web表单。这些对象应该精确地表示它所代表的实际外部事物的状态。包括缺陷和无效的胡言乱语。
通常情况下,您会有一个框架来验证这些内容。这就是“validation nonnull”的含义:该字段可以为null,如果您尝试将其设置为null,则不会发生任何异常(因此它们可以)。但是,只要该字段保持为null,如果您问我对象是否有效,答案是:“否”。
Lombok的@NonNull在不同的位置上意思不同,比较令人困惑。如果在参数上使用,它的意思是:Lombok,请在方法中生成明确的空值检查,除非我已经自己编写了该检查。
如果在字段上使用,它的意思是:“为了考虑@RequiredArgsConstructor,请将此字段视为必需;在生成的构造函数中进行空值检查(抛出异常)。此外,复制相关的空值注释,因此,如果为此字段创建setter,则在其中添加该空值检查。”
鉴于空值注释的含义完全不同,因此在代码中对同一结构应用多个此类注释可能并不荒唐。如果您希望DB引擎在将此类用作模板时生成SQL NON NULL约束,并且对于表示此表中行的任何对象立即拒绝将其置于无效状态的任何尝试,同时添加两者可能是有意义的。如果您希望接受对象表示无效状态的情况(如果一般原则是构造空对象,然后使用setter一个接一个地设置每个字段值),则不要添加Lombok的注释。

你肯定已经覆盖了所有的复杂性,对吧?

甚至没有一丝一毫。要想真正地头疼,考虑一下checkerframework的@PolyNull,并考虑这只是一个适当类型系统所能真正处理的内容的过度简化!

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


哦,PolyNull,那就像是关于空值的泛型,或者类似的东西。 - Mark VY

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