Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,因此JDK 最好下载 JDK 9以上的版本。java
在Java 8以前,编写在特定状况下没法返回任何值的方法时,能够采用两种方法。要么抛出异常,要么返回null(假设返回类型是对象是引用类型)。但这两种方法都不完美。应该为异常条件保留异常(条目 69),而且抛出异常代价很高,由于在建立异常时捕获整个堆栈跟踪。返回null没有这些缺点,可是它有本身的缺陷。若是方法返回null,客户端必须包含特殊状况代码来处理null返回的可能性,除非程序员可以证实null返回是不可能的。若是客户端忽略检查null返回并将null返回值存储在某个数据结构中,那么会在未来的某个时间在与这个问题不相关的代码位置上,抛出NullPointerException
异常的可能性。git
在Java 8中,还有第三种方法来编写可能没法返回任何值的方法。Optional<T>
类表示一个不可变的容器,它能够包含一个非null的T
引用,也能够什么都不包含。不包含任何内容的Optional被称为空(empty)。非空的包含值称的Optional被称为存在(present)。Optional的本质上是一个不可变的集合,最多能够容纳一个元素。Optional<T>
没有实现Collection<T>
接口,但原则上是能够。程序员
在概念上返回T的方法,但在某些状况下可能没法这样作,能够声明为返回一个Optional<T>
。这容许该方法返回一个空结果,以代表不能返回有效的结果。返回Optional的方法比抛出异常的方法更灵活、更容易使用,并且比返回null的方法更不容易出错。github
在条目 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
,那么Stream
的max
操做会为咱们生成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容许用户忽略这种可能性,从而带来潜在的可怕后果。可是,抛出一个检查异常须要在客户端中添加额外的样板代码。ide
若是方法返回一个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的方法来处理更特殊的用例: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
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做为属性存储在对象中是颇有意义的。
总之,若是发现本身编写的方法不能老是返回值,而且认为该方法的用户在每次调用时考虑这种可能性很重要,那么或许应该返回一个Optional的方法。可是,应该意识到,返回Optional会带来实际的性能后果;对于性能关键的方法,最好返回null或抛出异常。最后,除了做为返回值以外,不该该在任何其余地方中使用Optional。