读书笔记,《Java 8实战》,第四章,引入流
集合是Java中使用最多的API,但集合操作却远远算不上完美。主要表现在两点,
第一,集合不能让我们像数据库的SQL语言一样用申明式的语言指定操作;
第二,现在的集合API无法让我们比较方便地执行并行操作。
第一节,流是什么?
流是Java API的新成员,它允许你声明性的方式处理数据集合,也就是通过查询语句来表达,而不是临时编写一个实现。此外,流还可以透明的并行处理,你无需写任何多线程代码了。在这里,作者给了一个例子,分别用Java7和Java8来实现一个返回低热量的菜肴名称的例子,这里用到了流的parallelStream、filter、sorted、map、collect,共5个API。用Java8的实现,确实比较完美。简单易读。
总结来说就是,Java8中的流API可以让你写出这样的代码,
第一,申明性,更简洁,更易读。
第二,可复合,更灵活。
第三,可并行,性能更好。
第二节,流简介
我们可以从一个集合或者从数值的范围或者从I/O资源来生成流元素。
那么流到底是什么呢?简短的定义就是:从支持数据处理操作的源生成的元素序列。
集合是数据结构,所以它的主要目的是以特定的时间和空间复杂度存储和访问元素,但流的目的在于表达计算。
集合讲的是数据,流讲的是计算。
很多流操作本身也会返回一个流,这样多个操作就可以连接起来,形成一个大的流水线。就让我们下一章中的一些优化成为可能,比如延时和短路。
menu.stream().filter( d->d.getCalories() > 300).map(Dish::getName).limit(3).collect(toList());
在上面的流操作的例子中,我们最后给了一个collect操作,那么在调用collect之前其实没有任何结果产生。实际上根本就没有从菜单里面选择元素,你可以这么理解,链中的方法调用都在排队等待,直到调用了collect。
第三节,流与集合
集合与流之间的差异就在于什么时候进行计算。
集合中的每个元素都得先算出来才能添加到集合中。相比之下,流则是在概念上固定的数据结构,其元素则是按需计算的。换句话说,流就像是一个延迟创建的集合,只有在消费者要求的时候才会计算。
和迭代器类似,流只能被遍历一次。这里作者给出了一个对一个流遍历两次的例子,
1 List<String> title = Arrays.toList("Java8", "In", "Action"); 2 Stream<String> s = title.stream(); 3 s.forEach(System.out::println); 4 s.forEach(System.out::println);
此时程序就会抛出异常。
你可以把流看作在时间中分布的一组值,而集合则是在空间(内存)中分布的一组值。
接下来作者提到了内部迭代与外部迭代的概念,他就说明了流所引入的这种内部迭代所带来的好处。流对迭代做了封装,这样Stream库的内部结算就可以自动选择一种适合你的硬件的数据表示和并行的实现方式。
第四节,流操作
可以连接起来的流操作称为中间操作,比如filter,map,sorted,limit等。关闭流量操作,则称为终端操作,比如collect,count。
中间操作会返回一个流,这样多个操作可以连接起来形成一个查询,重要的是,除非流水线上触发了一个终端操作,否则中间操作不会执行任何处理,因为他们都很懒。
中间操作一般都可以合并起来,在终端操作时一次性全部处理。我们把这种技术叫做循环合并,作者在这里给了一个例子可以比较清楚地看出确实是有循环合并。
1 List<String> names = menu.stream().filter( d-> { 2 System.out.println("filtering" + d.getName()); 3 return d.getCalories() > 300; 4 }) 5 .map( d->{ 6 System.out.println("mapping" + d.getName()); 7 return d.getName(); 8 }) 9 .limit(3) 10 .collect(toList());
System.out.println(names());
终端操作从流的流水线生成结果,其结果是任何不是流的值,比如List、Integer、甚至是void都是可以的。
总而言之,流的使用一般包括三件事:
一、数据源,来执行一个查询,
二、中间操作链,来形成一条流的流水线,
三、终端操作,来执行流水线,并生成结果。