很久以前,有一个函数。
public static Foo getFoo(Bar bar) {
return bar.getFoo();
}
人们对于如果bar为空时应该发生什么意见不一致。
首先,会有人声称违反函数意图应该受到检查异常的惩罚。
public static Foo getFoo(Bar bar) throws FooNotFoundException {
if (bar == null) throw new FooNotFoundException();
return bar.getFoo();
}
函数的调用者将被迫考虑这种情况。它必须捕获异常或重新抛出异常。这样做是如此强制性的,以至于它会令人烦恼,不久之后人们会争论这不应该是一个受检异常,而是一个运行时异常,以使其 less forceful。
public static Foo getFoo(Bar bar) {
if (bar == null) throw new FooNotFoundException();
return bar.getFoo();
}
有人会认为抛出异常没有意义,因为Java本身已经会抛出一个异常:空指针异常。
那些担心意外异常的人会使他们的代码更加健壮,并在函数开头添加null
检查,这将简单地返回null
。
public static Foo getFoo(Bar bar) {
if (bar == null) return null;
return bar.getFoo();
}
不久之后,人们会争论是否可以返回空列表,或者这些空列表是否应该被视为null
值。 "当然,你不想在每次迭代之前检查可空性,对吧?" 另一方会反驳。很快,他们将创建各种构造来完全避免空值问题。
public static Foo getFoo(Bar bar) {
if (bar == null) return Foo.Empty;
return bar.getFoo();
}
每种方法都会产生广泛的后果。这些后果将使得不同方法的结合变得困难。这些后果将导致编码规则,其中每个单独的规则都与下一个规则相连。它们相互支持的方式会给人留下一种印象,即每个规则都是无可辩驳的。最终,
编码规则会变成像宗教一样,其推理只在完整规则集的范围内才有意义。
根据您选择的规则集,使用某些框架可能会遇到困难。
最终,带有未经检查的异常的空列表宗教成为了主导。 这种宗教可以概括如下:
- 应避免返回
null
值。
- 如果列表为空,则按原样返回。
- 如果迭代列表,则无需检查null。
- 方法不应抛出已检查异常
- 应将已检查异常包装在运行时异常中。
- 字符串不应该为空,而应该是
""
。
显然,这种宗教变得如此强大,以至于它设法影响了框架和语言规范。
- 针对空列表的编译器优化
Optional
类
- 值类型
一些外部库和编辑器实际上会尝试通过提供注释(@Null
和@NotNull
)来重新联合不同的团队。IDE将为您标记所有违规行为。这是一个简单但有效的解决方案。然而,JDK从未包含自己的@Null
或@NotNull
,而是每个库都必须提供自己的注释。
考虑到所有这些,现在很可能永远不会在Java中出现Elvis操作符了。如果想在Java中编码,最好忘记null
。
或者用Tony Hoare(null的发明者)的话说:
我把它称为我的十亿美元错误。这是1965年发明null引用的时候。那时,我正在设计一个面向对象语言(ALGOL W)中引用的第一个全面类型系统。我的目标是确保所有引用使用都绝对安全,并由编译器自动执行检查。但是,我无法抗拒放置null引用的诱惑,因为它非常容易实现。这导致了无数的错误,漏洞和系统崩溃,在过去的四十年中可能造成了数十亿美元的痛苦和损失。
个人认为,鉴于每种体面的编程语言都有null,这绝对没有任何意义。一些甚至有多个null值来表示不同类型的nullability。毕竟,甚至在数学中也有未定义的值。
无论如何,如果您没有Elvis操作符,仍然可以
Foo foo = bar == null ? null : bar.getFoo();
这正好符合 Java 的精神。毕竟,在 2021 年,Java 是一种非常明确的语言。