Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。

45. 明智审慎地使用Stream
在Java 8中添加了Stream API,以简化顺序或并行执行批量操作的任务。 该API提供了两个关键的抽象:流(Stream),表示有限或无限的数据元素序列,以及流管道(stream pipeline),表示对这些元素的多级计算。 Stream中的元素可以来自任何地方。 常见的源包括集合,数组,文件,正则表达式模式匹配器,伪随机数生成器和其他流。 流中的数据元素可以是对象引用或基本类型。 支持三种基本类型:int,long和double。
流管道由源流(source stream)的零或多个中间操作和一个终结操作组成。每个中间操作都以某种方式转换流,例如将每个元素映射到该元素的函数或过滤掉所有不满足某些条件的元素。中间操作都将一个流转换为另一个流,其元素类型可能与输入流相同或不同。终结操作对流执行最后一次中间操作产生的最终计算,例如将其元素存储到集合中、返回某个元素或打印其所有元素。
管道延迟(lazily)计算求值:计算直到终结操作被调用后才开始,而为了完成终结操作而不需要的数据元素永远不会被计算出来。 这种延迟计算求值的方式使得可以使用无限流。 请注意,没有终结操作的流管道是静默无操作的,所以不要忘记包含一个。
Stream API流式的(fluent)::它设计允许所有组成管道的调用被链接到一个表达式中。事实上,多个管道可以链接在一起形成一个表达式。
默认情况下,流管道按顺序(sequentially)运行。 使管道并行执行就像在管道中的任何流上调用并行方法一样简单,但很少这样做(第48个条目)。
Stream API具有足够的通用性,实际上任何计算都可以使用Stream执行,但仅仅因为可以,并不意味着应该这样做。如果使用得当,流可以使程序更短更清晰;如果使用不当,它们会使程序难以阅读和维护。对于何时使用流没有硬性的规则,但是有一些启发。
考虑以下程序,该程序从字典文件中读取单词并打印其大小符合用户指定的最小值的所有变位词(anagram)组。如果两个单词由长度相通,不同顺序的相同字母组成,则它们是变位词。程序从用户指定的字典文件中读取每个单词并将单词放入map对象中。map对象的键是按照字母排序的单词,因此『staple』的键是『aelpst』,『petals』的键也是『aelpst』:这两个单词就是同位词,所有的同位词共享相同的依字母顺序排列的形式(或称之为alphagram)。map对象的值是包含共享字母顺序形式的所有单词的列表。 处理完字典文件后,每个列表都是一个完整的同位词组。然后程序遍历map对象的values()的视图并打印每个大小符合阈值的列表:
// Prints all large anagram groups in a dictionary iteratively public class Anagrams { public static void main(String[] args) throws IOException { File dictionary = new File(args[0]); int minGroupSize = Integer.parseInt(args[1]); Map<String, Set<String>> groups = new HashMap<>(); try (Scanner s = new Scanner(dictionary)) { while (s.hasNext()) { String word = s.next(); [groups.computeIfAbsent(alphabetize(word](http://groups.computeIfAbsent(alphabetize(word)), (unused) -> new TreeSet<>()).add(word); } } for (Set<String> group : groups.values()) if (group.size() >= minGroupSize) System.out.println(group.size() + ": " + group); } private static String alphabetize(String s) { char[] a = s.toCharArray(); Arrays.sort(a); return new String(a); } }这个程序中的一个步骤值得注意。将每个单词插入到map中(以粗体显示)中使用了computeIfAbsent方法,该方法是在Java 8中添加的。这个方法在map中查找一个键:如果键存在,该方法只返回与其关联的值。如果没有,该方法通过将给定的函数对象应用于键来计算值,将该值与键关联,并返回计算值。computeIfAbsent方法简化了将多个值与每个键关联的map的实现。
现在考虑以下程序,它解决了同样的问题,但大量过度使用了流。 请注意,整个程序(打开字典文件的代码除外)包含在单个表达式中。 在单独的表达式中打开字典文件的唯一原因是允许使用try-with-resources语句,该语句确保关闭字典文件:
// Overuse of streams - don't do this! public class Anagrams { public static void main(String[] args) throws IOException { Path dictionary = Paths.get(args[0]); int minGroupSize = Integer.parseInt(args[1]);
