空对象设计模式 vs 空对象检查

11
为什么空对象设计模式比空对象检查更好?
如果我们查看空对象设计模式中的内存占用情况,我们会创建一个相同类型的虚拟对象。这意味着,如果我们有一个大尺寸对象和大量可为空的对象在搜索查询中,这个模式将创建大量的空对象,将占用比简单的空检查更多的内存,而简单的空检查可能只会在性能上带来可以忽略的延迟成本。 空对象设计模式

3
在Java 8中,您可以使用Optional来避免执行额外的检查而避免NullPointerException。http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html - Stultuske
其他编程语言如C#和C++呢? - Muhammad Nasir
4
随着经验增加,你将会越来越意识到一个速度超快但不工作或充满漏洞的程序比一个运行良好且足够快的程序更糟糕。并非所有事情都与性能和内存占用有关,避免漏洞才更为重要。null是许多漏洞的根源,而检查null常常被忽略。 - JB Nizet
@MuhammadNasir 我同意JB Nizet的观点。这不是像“A总是比B好”的事情。你总是需要考虑上下文。当然,在内嵌环境中,由于内存容量非常有限,你会寻找小内存占用。但在现代服务器上,这可能不是你的主要关注点。 - Fildor
3
个人而言,我更喜欢使用 null 而不是使用包装对象来处理值。始终应该对返回的值进行 null 检查。编译器将自动优化任何多余的 null 检查代码。您总是希望将 null(无论是 null 还是 Null)分为不同的程序流程。如果对象为空,我永远不会让它通过各种 hoops。一定要先进行检查,然后就容易分离程序流程和提供自定义错误消息了。与使用 Null 对象相关的开销和更复杂的编程对我来说不值得。您始终需要检查两者。 - Tschallacka
4个回答

8
整个问题在于如果尝试访问null值,应用程序将抛出NullPointerException并中止。
为了减少在这个“空对象设计模式”中的NullXXX类的数量(它实际上只是工厂设计模式,而不是模式本身),您可以创建一个始终被返回的static final NullCustomer
在Java 8中,您可以使用Optional方法来告诉函数并非总是返回值。此方法不强制您创建任意的空类,从而污染整体结构(还需考虑可能需要重构这些空类)。 EclipseIntelliJ还提供编译时注释@Nullable@NonNull,当访问潜在的null对象时给出编译器警告。然而,许多框架没有被注释。因此,IntelliJ尝试通过静态分析发现这些潜在的空访问
除了这种方法的低采用率外,IntelliJ和Eclipse使用他们自己的注释(org.eclipse.jdt.annotation.NonNullcom.intellij.annotations.NotNull),它们不兼容。但是,您可以将注释存储在代码之外,这在IntelliJ中有效。Eclipse也计划在未来实现这一点。问题在于有许多框架提供了此功能,给您提供了许多不同的注释来执行相同的操作。曾经有一个名为JSR-305的项目已处于休眠状态,它会提供一个位于javax中的注释。我不知道他们没有推动这一点的原因。

仅为更正此处的内容:“Eclipse也希望在未来实现这一点”的说法:通过Eclipse Mars(4.5)发布了对外部注释的支持。 - Stephan Herrmann
另一个澄清是:“IntelliJ和Eclipse使用它们自己的注释(org.eclipse.jdt.annotation.NonNull、com.intellij.annotations.NotNull),它们不兼容。” 在这些注释中只有一件事可能不兼容:@Target。 Eclipse有两个版本,org.eclipse.jdt.annotation_1.x用于1.7及以下版本(“声明注释”),org.eclipse.jdt.annotation_2.x用于1.8及以上版本(“类型注释”,其中目标为TYPE_USE,实现更强的检查)。除此之外,Eclipse可以配置为使用任何正确定义的null注释集。 - Stephan Herrmann
只是为了补充答案,使用 Optional 通常是正确的解决方案,但当您使用返回的对象来回答一堆不同的问题时,每个答案都依赖于返回对象字段的值时,将逻辑委托给 Null Object 会显着更容易维护。这仅限于那些 1/20 的情况;其他 19/20 通常最好使用 Optional。 - oligofren

1
使用Null Object相比于使用null的主要优势在于,使用null时你必须反复检查该对象是否确实为null,特别是在需要该对象的所有方法中。
在Java 8中,你将不得不执行以下操作:
Object o = Objects.requireNotNull(o); //Throws NullPointerException if o is indeed null.

因此,如果您有一个方法不断将相同的对象传递给各种方法,则每个方法在使用它之前都需要检查接收到的对象是否为null。
因此,更好的方法是拥有一个Null Object或Optional(Java 8及更高版本),这样您就不需要一直进行空检查。而是应该:
Object o = optional.get(); //Throws NullPointerException if internal value is indeed null.
//Keep using o.

不需要进行空值检查。有了Optional,意味着你可能有值也可能没有。

空对象通常没有任何副作用,因为它通常什么都不做(通常所有的方法都是空方法),所以不需要担心性能问题(瓶颈/优化等)。


1
你仍然需要检查Optional是否存在,而NullPointerException仍然是你想要避免的。只是因为它是一个Optional,现在显然它可能不存在。 - JB Nizet
@JB Nizet确实。我试图强调每次都要进行null检查的缺点。 - Buhake Sindi
使用空注释,你应该将所有那些方法标记为需要一个非空参数。然后调用者只需在本地变量上执行一次空检查/断言,并将此变量传递给所有方法调用即可。此外:这避免了对于期望执行某些操作的方法调用可能是无效操作而导致的潜在混淆。 - Stephan Herrmann

0

这种模式的主要区别(也许是优势)是它的独特性。考虑下面的方法定义:

public static int length(String str);

这个方法用于计算给定字符串的长度。但是参数可以是null吗?这个方法会怎么做呢?抛出异常?返回0?返回-1?我们不知道。

通过编写良好的Java文档,可以实现部分解决方案。下一个稍微好一点的解决方案是使用JSR305注解@Nullable@NotNullable,但是开发人员可能会忽略它们。

然而,如果您正在使用空对象模式(例如Guava或Java 8中的Optional),您的代码将如下所示:

public static int length(Optional<String> str);

所以开发者必须关心将字符串包装成Optional,并且理解参数可能为空。尝试从包含null的Optional中获取值会导致异常,而在处理常规的null时并不总是发生这种情况。

显然,你是对的,使用这种模式会导致一些额外的CPU和内存消耗,但在大多数情况下并不重要。


空对象模式(Null Object Pattern)并不等同于可选类型(Optional)。 - Rodrigo Ruiz

0
假设你有这样的东西:
private SomeClass someField;

void someMethod() {
  // some other code
  someField.method1();
  // some other code
  someField.method2();
  // some other code
  someField.method3();
}

现在假设当someField可以为null时有有效的用例,您不想调用它的方法,但是您想执行方法中的其他一些其他代码部分。您需要将该方法实现为:

void someMethod() {
  // do something
  if (someField != null) {
    someField.method1();
  }
  // do something
  if (someField != null) {
    someField.method2();
  }
  // do something
  if (someField != null) {
    someField.method3();
  }
}

通过使用带有空(无操作)方法的Null对象,我们避免了样板null检查(以及忘记为所有出现添加检查的可能性)。
我经常在一些异步或可选初始化的情况下发现这很有用。

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