Tips

书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code

注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

55. 明智而审慎地返回Optional

在Java 8之前,编写在特定情况下无法返回任何值的方法时,可以采用两种方法。要么抛出异常,要么返回null(假设返回类型是对象是引用类型)。但这两种方法都不完美。应该为异常条件保留异常(条目 69),并且抛出异常代价很高,因为在创建异常时捕获整个堆栈跟踪。返回null没有这些缺点,但是它有自己的缺陷。如果方法返回null,客户端必须包含特殊情况代码来处理null返回的可能性,除非程序员能够证明null返回是不可能的。如果客户端忽略检查null返回并将null返回值存储在某个数据结构中,那么会在将来的某个时间在与这个问题不相关的代码位置上,抛出NullPointerException异常的可能性。

在Java 8中,还有第三种方法来编写可能无法返回任何值的方法。Optional<T>类表示一个不可变的容器,它可以包含一个非null的T引用,也可以什么都不包含。不包含任何内容的Optional被称为空(empty)。非空的包含值称的Optional被称为存在(present)。Optional的本质上是一个不可变的集合,最多可以容纳一个元素。Optional<T> 没有实现 Collection<T>接口,但原则上是可以。

在概念上返回T的方法,但在某些情况下可能无法这样做,可以声明为返回一个Optional<T>。这允许该方法返回一个空结果,以表明不能返回有效的结果。返回Optional的方法比抛出异常的方法更灵活、更容易使用,而且比返回null的方法更不容易出错。

在条目 30中,我们展示了根据集合中元素的自然顺序计算集合最大值的方法。

// Returns maximum value in collection - throws exception if empty
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("Empty collection"); E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}

如果给定集合为空,此方法将抛出IllegalArgumentException异常。我们在条目30中提到,更好的替代方法是返回Optional<E>。下面是修改后的方法:

// Returns maximum value in collection as an Optional<E>
public static <E extends Comparable<E>>
Optional<E> max(Collection<E> c) {
if (c.isEmpty())
return Optional.empty(); E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return Optional.of(result);
}

如你所见,返回Optional很简单。 你所要做的就是使用适当的静态工厂创建Optional。 在这个程序中,我们使用两个:Optional.empty()返回一个空的Optional,Optional.of(value)返回一个包含给定非null值的Optional。 将null传递给Optional.of(value)是一个编程错误。 如果这样做,该方法通过抛出NullPointerException异常作为回应。 Optional.of(value)方法接受一个可能为null的值,如果传入null则返回一个空的Optional。永远不要通过返回Optional的方法返回一个空值:它破坏Optional设计的初衷。

Stream上的很多终止操作返回Optional。如果我们重写max方法来使用一个Stream,那么Streammax操作会为我们生成Optional的工作(尽管我们还是传递一个显式的Comparator):

// Returns max val in collection as Optional<E> - uses stream
public static <E extends Comparable<E>>
Optional<E> max(Collection<E> c) {
return c.stream().max(Comparator.naturalOrder());
}

那么,如何选择返回Optional而不是返回null或抛出异常呢?Optional在本质上类似于检查异常(checked exceptions)(条目 71),因为它们迫使API的用户面对可能没有返回任何值的事实。抛出未检查的异常或返回null允许用户忽略这种可能性,从而带来潜在的可怕后果。但是,抛出一个检查异常需要在客户端中添加额外的样板代码。

如果方法返回一个Optional,则客户端可以选择在方法无法返回值时要采取的操作。 可以指定默认值:

// Using an optional to provide a chosen default value
String lastWordInLexicon = max(words).orElse("No words...");

或者可以抛出任何适当的异常。注意,我们传递的是异常工厂,而不是实际的异常。这避免了创建异常的开销,除非它真的实际被抛出:

// Using an optional to throw a chosen exception
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);

如果你能证明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的方法来处理更特殊的用例:filtermapflatMapifPresent。在Java 9中,又添加了两个这样的方法:orifPresentOrElse。如果上面描述的基本方法与你的用例不太匹配,请查看这些更高级方法的文档,并查看它们是否能够完成任务。

如果这些方法都不能满足你的需要,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是OptionalIntOptionalLongOptionalDouble。它们包含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作为属性存储在对象中是很有意义的。

总之,如果发现自己编写的方法不能总是返回值,并且认为该方法的用户在每次调用时考虑这种可能性很重要,那么或许应该返回一个Optional的方法。但是,应该意识到,返回Optional会带来实际的性能后果;对于性能关键的方法,最好返回null或抛出异常。最后,除了作为返回值之外,不应该在任何其他地方中使用Optional。

Effective Java 第三版——55. 明智而审慎地返回Optional的更多相关文章

  1. Effective Java 第三版——52. 明智而审慎地使用重载

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  2. Effective Java 第三版——53. 明智而审慎地使用可变参数

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  3. Effective Java 第三版——45. 明智审慎地使用Stream

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Effective Java 第三版——67. 明智谨慎地进行优化

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  5. Effective Java 第三版——66. 明智谨慎地使用本地方法

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  6. Effective Java 第三版——83. 明智谨慎地使用延迟初始化

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  7. 《Effective Java 第三版》目录汇总

    经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...

  8. 《Effective Java 第三版》新条目介绍

    版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...

  9. Effective Java 第三版——19. 如果使用继承则设计,并文档说明,否则不该使用

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

随机推荐

  1. 【UER #1】DZY Loves Graph(待卡常数)

    题解: 正解是可持久化并查集 但这个显然是lct可以维护的 但这常数是个问题啊??? #include <bits/stdc++.h> using namespace std; struc ...

  2. poj1743

    题解: 后缀数组+二分答案 首先会发现这题实质上就是求最长不重复的相同子段 首先二分答案长度,之后对每一段信息进行维护 一段信息即保证这一段的sa值都大于mid即可 然后找到这段中后缀位置最大和最小处 ...

  3. Codeforces 739C Alyona and towers 线段树

    Alyona and towers 这个题写起来真的要人命... 我们发现一个区间被加上一个d的时候, 内部的结构是不变的, 改变的只是左端点右端点的值, 这样就能区间合并了. 如果用差分的话会简单一 ...

  4. Codeforces 535D - Tavas and Malekas

    535D - Tavas and Malekas 题目大意:给你一个模板串,给你一个 s 串的长度,告诉你 s 串中有 m 个模板串并告诉你,他们的其实位置, 问你这样的 s 串总数的多少,答案对1e ...

  5. P1541 乌龟棋 线性dp

    题目背景 小明过生日的时候,爸爸送给他一副乌龟棋当作礼物. 题目描述 乌龟棋的棋盘是一行NN个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第NN格是终点,游戏要求玩家控制一个乌龟棋子 ...

  6. 敌兵布阵 HDU1166

    基础线段树 #include<cstdio> #include<iostream> using namespace std; int n,p,a,b,m,x,y,ans; st ...

  7. 【Java】 剑指offer(4) 替换空格

    本文参考自<剑指offer>一书,代码采用Java语言.  更多:<剑指Offer>Java实现合集 题目 请实现一个函数,把字符串中的每个空格替换成"%20&quo ...

  8. 【猿分享第10期】微信小程序Meetup扫盲专场回顾(转载)

    首先感谢答疑师:子慕 前端工程师,目前就职于医联,偶尔写点博客,吐槽总结,偶尔吟“湿”作对,润滑万物,江湖人称子慕大诗人. 直播间语音回放收听,请微信扫描下图二维码授权进入即可. 以下为本次直播的全部 ...

  9. nodejs那些事儿

    http://www.nodeclass.com/ https://cnodejs.org/ 当前版本,v6.11.2 安装node时,牵扯features的选择,在不了解的情况下,我选择了第1个.网 ...

  10. cf348D. Turtles(LGV定理 dp)

    题意 题目链接 在\(n \times m\)有坏点的矩形中找出两条从起点到终点的不相交路径的方案数 Sol Lindström–Gessel–Viennot lemma的裸题? 这个定理是说点集\( ...