Effective Java 第三版——55. 明智而审慎地返回Optional (2)

如果你能证明Optional非空,你可以从Optional获取值,而不需要指定一个操作来执行。但是如果Optional是空的,你判断错了,代码会抛出一个NoSuchElementException异常:

// Using optional when you know there’s a return value Element lastNobleGas = max(Elements.NOBLE_GASES).get();

有时候,可能会遇到这样一种情况:获取默认值的代价很高,除非必要,否则希望避免这种代价。对于这些情况,Optional提供了一个方法,该方法接受Supplier<T>,并仅在必要时调用它。这个方法被称为orElseGet,但是或许应该被称为orElseCompute,因为它与以compute开头的三个Map方法密切相关。有几个Optional的方法来处理更特殊的用例:filter、map、flatMap和ifPresent。在Java 9中,又添加了两个这样的方法:or和ifPresentOrElse。如果上面描述的基本方法与你的用例不太匹配,请查看这些更高级方法的文档,并查看它们是否能够完成任务。

如果这些方法都不能满足你的需要,Optional提供isPresent()方法,可以将其视为安全阀。如果Optional包含值,则返回true;如果为空,则返回false。你可以使用此方法对可选结果执行任何喜欢的处理,但请确保明智地使用它。isPresent的许多用途都可以被上面提到的一种方法所替代。生成的代码通常更短、更清晰、更符合习惯。

例如,请考虑此代码段,它打印一个进程的父进程ID,如果进程没有父进程,则打印N/A. 该代码段使用Java 9中引入的ProcessHandle类:

Optional<ProcessHandle> parentProcess = ph.parent(); System.out.println("Parent PID: " + (parentProcess.isPresent() ? String.valueOf(parentProcess.get().pid()) : "N/A"));

上面的代码可以被如下代码所替代,使用了Optional的map方法:

System.out.println("Parent PID: " + ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));

当使用Stream进行编程时,通常会发现使用的是一个Stream<Optional<T>>,并且需要一个Stream<T>,其中包含非Optional中的所有元素,以便继续进行。如果你正在使用Java 8,下面是弥补这个差距的代码:

streamOfOptionals .filter(Optional::isPresent) .map(Optional::get)

在Java 9中,Optional配备了一个stream()方法。这个方法是一个适配器, 此方法是一个适配器,它将Optional变为包含一个元素的Stream,如果Optional为空,则不包含任何元素。此方法与Stream的flatMap方法(条目45)相结合,这个方法可以简洁地替代上面的方法:

streamOfOptionals. .flatMap(Optional::stream)

并不是所有的返回类型都能从Optional的处理中获益。容器类型,包括集合、映射、Stream、数组和Optional,不应该封装在Optional中。与其返回一个空的Optional<List<T>>,不还如返回一个空的List<T>(条目 54)。返回空容器将消除客户端代码处理Optional的需要。ProcessHandle类确实有arguments方法,它返回Optional<String[]>,但是这个方法应该被视为一种异常,不该被效仿。

那么什么时候应该声明一个方法来返回Optional <T>而不是T呢? 通常,如果可能无法返回结果,并且在没有返回结果,客户端还必须执行特殊处理的情况下,则应声明返回Optional 的方法。也就是说,返回Optional <T>并非没有成本。 Optional是必须分配和初始化的对象,从Optional中读取值需要额外的迂回。 这使得Optional不适合在某些性能关键的情况下使用。 特定方法是否属于此类别只能通过仔细测量来确定(条目 67)。

与返回装箱的基本类型相比,返回包含已装箱基本类型的Optional的代价高得惊人,因为Optional有两个装箱级别,而不是零。因此,类库设计人员认为为基本类型int、long和double提供类似Option是合适的。这些Option是OptionalInt、OptionalLong和OptionalDouble。它们包含Optional<T>上的大多数方法,但不是所有方法。因此,除了“次要基本类型(minor primitive types)”Boolean,Byte,Character,Short和Float之外,永远不应该返回装箱的基本类型的Optional

到目前为止,我们已经讨论了返回Optional并在返回后处理它们的方法。我们还没有讨论其他可能的用法,这是因为大多数其他Optional的用法都是可疑的。例如,永远不要将Optional用作映射值。如果这样做,则有两种方法可以表示键(key)在映射中逻辑上的缺失:键要么不在映射中,要么存在的话映射到一个空的Optional。这反映了不必要的复杂性,很有可能导致混淆和错误。更通俗地说,在集合或数组中使用Optional的键、值或元素几乎都是不合适的。

这里留下了一个悬而未决的大问题。在实例中存储Optional属性是否合适吗?通常这是一种“不好的味道”:它建议你可能应该有一个包含Optional属性的子类。但有时这可能是合理的。考虑条目2中的NutritionFacts类的情况。NutritionFacts实例包含许多不需要的属性。不可能为这些属性的每个可能组合都提供一个子类。此外,属性包含基本类型,这使得很难直接表示这种缺失。对于NutritionFacts最好的API将为每个Optional属性从getter方法返回一个Optional,因此将这些Optional作为属性存储在对象中是很有意义的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zysywd.html