65.9K
CodeProject 正在变化。 阅读更多。
Home

Null 是可选的

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (33投票s)

2015年10月14日

CPOL

3分钟阅读

viewsIcon

71184

Null 是可选的。

引言

Null 引用通常用于表示值的缺失。然而,在大多数语言中(更具体地说,是在允许可空引用而没有显式语法的静态类型语言中),它存在一定的缺点。让我们来看以下方法:

User findUser(String name);

当找不到该用户时会发生什么?返回 null 可能是以最简单的方式表达缺失值的概念,但它有一个特定的缺点:null 引用的可能性是隐式的,接口没有揭示这一点。为什么这很重要?null 引用需要处理,否则在运行时可能会发生 NullPointerException,这是一种常见的错误。

findUser("jonhdoe").address(); // NullPointerException at runtime

通常,并非系统中的所有值都是可空的。指示确切需要处理的地方,有助于减少风险和不必要的努力。文档或元代码可以传达可空性,但它们并非理想选择:可能会遗漏,并且缺乏编译安全性。

有些语言默认情况下不允许 nullable 引用,并且有特殊的语法来允许它们。大多数语言无法做到这一点——在大多数情况下是为了兼容性原因——并且有另一种方法来解决这个问题,即利用它们的类型系统:选项类型。了解这种模式很有用,因为它可以很容易地在大多数语言中实现——如果尚未实现的话。例如,让我们看看 Java 8 的 Optional:“一个容器对象,可能包含也可能不包含非空值。”

Optional<User> findUser(String name);

这个接口清楚地表明结果中可能不存在用户,并且客户端必须考虑到这一点。

// findUser("jonhdoe").address(); compile error!

处理缺失值

Optional<User> user = findUser("johndoe");
if(user.isPresent()) {
  user.get().address();
}

这个例子有意保持与通常处理空引用的方式有些相似。更惯用的方法是:

findUser("johndoe").ifPresent(user -> user.address());

有趣的是,可以考虑该模式在系统更广泛环境中的影响。通过一致使用 Optional,可以建立一种强大的约定,即避免使用 null 引用。它将接口从

interface User {
  String name();
  Address address();
  BankAccount account();
}

to

interface User {
  String name();
  Address address();
  Optional<BankAccount> account();
}

转换为:客户端可以看到用户可能没有 银行账户,并且可以假设它始终具有 姓名地址(这里起作用的约定)。领域模型变得更具表现力。这种做法在变更时效果很好:例如,如果 地址 在未来某个时候变为可选,所有客户端代码都将被强制符合。到目前为止的代码示例展示了对方法签名的影响,相同的优点也适用于类字段或局部变量。

class Address {
  String city;
  Optional<String> street;
}

作为一个小小的奖励,还有一些更多的语法糖可以简化许多场景下的代码。

Optional<String> foo = Optional.ofNullable(thirdPartyApiThatMayReturnNull());
String foo2 = foo.orElse("No value.. take this default one.");
String foo3 = foo.orElseThrow(() -> new RuntimeException("I really can't work without a value though"));
thirdPartyApiExpectingNull(foo.orElse(null));
if(foo.equals(foo2)) { // no NullPointerException, even if foo is absent
  // ...
}

结论

在大多数情况下,尽可能避免隐式的 null 值可以对代码库产生奇迹般的效果,使其成为一个更安全的地方。缺点?像选项类型这样的模式在大多数情况下效果很好,但正如所有事物一样,也有例外:它是一个堆对象,如果对象数量非常多,则需要考虑这一点。在某些特定场景下,这种做法可能无法带来实际价值或不切实际。第三方代码也可能强制使用 null 引用。

一个使用可选类型的例子:Java 8 的 Stream API

© . All rights reserved.