许多方法返回元素序列。 在Java 8之前,这些方法的明显返回类型是集合接口Collection,Set和List;Iterable; 和数组类型。通常,很容易决定返回哪些类型。准确来说是一个集合接口。如果该方法仅用于启用for-each循环或返回的序列无法实现某些Collection方法(通常为contains(Object)),则使用Iterable接口。如果返回的元素是基本类型值或者存在严格的性能要求,则使用数组。在Java 8中,流被添加到平台中,这使得为返回序列的方法选择恰当的返回类型的任务变得非常复杂。
你可能听说过,流现在是返回一系列元素的公认选择,正如第45项所描述的,流不会使迭代过时:编写好的代码需要适当地组合流和迭代。如果API只返回一个流,而某些用户想要使用for-each循环迭代返回的序列,那么这些用户理所当然会感到不安。特别令人沮丧的是,Stream接口包含Iterable接口中唯一的抽象方法,Stream的此方法规范与Iterable兼容。
可悲的是,这个问题没有好的解决方法。乍一看,似乎可以将方法引用传递给Stream的迭代器方法。 结果代码可能有点嘈杂和模糊,但并非不合理:
// Won't compile, due to limitations on Java's type inference
for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) {
// Process the process
}
不幸的是,如果你尝试编译此代码,你将收到一条错误消息:
Test.java:6: error: method reference not expected here
for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) {
^
为了使代码编译,你必须将方法引用强制转换为适合参数化的Iterable:
// Hideous workaround to iterate over a stream
for (ProcessHandle ph : (Iterable<ProcessHandle>) ProcessHandle.allProcesses()::iterator)
此客户端代码有效,但在实践中使用它太嘈杂和模糊。更好的解决方法是使用适配器方法。JDK没有提供这样的方法,但是使用上面的代码片段中使用的相同技术,可以很容易地编写一个方法。请注意,在适配器方法中不需要强制转换,因为Java类型推断在此上下文中正常工作:
// Adapter from Stream<E> to Iterable<E>
public static <E> Iterable<E> iterableOf(Stream<E> stream) {
return stream::iterator;
}
使用此适配器,你可以使用for-each语句迭代任何流:
for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) {
// Process the process
}
请注意,第34项中的Anagrams程序的流版本使用Files.lines方法读取字典,而迭代版本使用scanner。Files.lines方法优于scanner,它可以在读取文件时悄悄地处理(silently swallows)任何异常。理想情况下,我们也会在迭代版本中使用Files.lines。如果API仅提供对序列的流访问并且他们希望使用for-each语句迭代序列,那么程序员将会做出这种折中的方法【在迭代版本中使用Files.lines】。
相反,想要使用流管道处理序列的程序员理所当然会因为API仅提供Iterable而感到难过【傲娇】。再提一次JDK没有提供适配器,但编写一个是很容易的:
// Adapter from Iterable<E> to Stream<E>