几乎每一个 Java 应用都会制造和处理集合。流
容许咱们以声明性方式处理集合(经过相似于 SQL 查询语句来表达,而不是临时写一个实现)。此外 流
还能够透明地并行处理,无需写任何多线程代码。java
先看一个例子,混混眼熟就行数据库
List<String> result = dishList .parallelStream() // 过滤 .filter(d -> d.getCalories() < 400) // 排序 .sorted(Comparator.comparing(Dish::getCalories)) // 映射 .map(Dish::getName) // 保存 .collect(Collectors.toList());
并且以 parallelStream 调用会利用多核架构并行执行这段代码,若是想要单线程处理,只须要把 parallelStream 换成 stream。数组
从代码能够看出,它是以声明性方式写的:filter、sorted、map 和 collect。如同 SQL 中的 SELECT、FROM、WHERE 同样。数据结构
Java 8中的 Stream API 能够多线程
声明性:更简洁架构
可复合:更灵活函数
可并行:性能更好性能
流的定义是 从支持数据处理操做的源生成的元素序列
。this
元素序列:流提供了一个接口,能够访问特定元素类型的一组有序值线程
源:流会使用一个提供数据的源,如集合、数组或输出/输出
数据处理操做:流的数据处理功能支持相似数据库的声明式操做,能够串行处理也可并行
流水线:不少流操做自己会返回一个流,这样多个操做就能够连接起来,造成一个大的流水线
内部迭代:与使用迭代器显式迭代不一样,流的迭代操做是在背后执行的
为了更直观地理解流以及 Lambda 在其的应用,咱们能够先建立一个菜肴类
public class Dish { // 名字 private String name; // 是否素食 private Boolean vegetarian; // 卡路里 private Integer calories; // 类型 private Type type; public Dish(String name, Boolean vegetarian, Integer calories, Type type) { this.name = name; this.vegetarian = vegetarian; this.calories = calories; this.type = type; } // 类型:肉、鱼、其余 public enum Type { MEAT, FISH, OTHER } // Getter and Setter }
而后用构造函数定义一个菜单 List,好让咱们对其进行流的演示
List<Dish> menu = new ArrayList<>(); // 猪肉:非素食,800卡路里,肉类 menu.add(new Dish("pork", false, 800, Dish.Type.MEAT)); // 牛肉:非素食,700卡路里,肉类 menu.add(new Dish("beef", false, 700, Dish.Type.MEAT)); // 米饭:素食,359卡路里,其余 menu.add(new Dish("rice", true, 350, Dish.Type.OTHER)); // 披萨:素食,550卡路里,其余 menu.add(new Dish("pizza", true, 550, Dish.Type.OTHER)); // 对虾:非素食,350卡路里,鱼类 menu.add(new Dish("prawns", false, 350, Dish.Type.FISH)); // 三文鱼:非素食,450卡路里,鱼类 menu.add(new Dish("salmon", false, 450, Dish.Type.FISH));
接下来咱们使用流找出头三个高热量菜肴的名字
List<String> threeHighCaloriesDishNames = menu // 从 menu 得到源 .stream() // Lambda 调用谓词函数式接口过滤卡路里大于400的高热量菜肴 .filter((dish) -> dish.getCalories() > 400) // 将过滤结果映射成对应的菜肴名 .map(Dish::getName) // 按照顺序选择三个 .limit(3) // 保存成 List .collect(Collectors.toList());
其中两个 Lambda 对应的是如下的快捷写法
// 返回一个判断卡路里是否高于400的谓词接口对象 Predicate<Dish> dishPredicate = (Dish dish) -> dish.getCalories() > 400; // 返回一个映射菜肴名称的映射接口对象 Function<Dish, String> dishStringFunction = (Dish dish) -> dish.getName();
若是看不懂请回顾上两章内容。涉及到行为参数化和 Lambda 的多种可简写方式。
在这个小小的例子里,有不少概念。咱们先是对 menu 调用 stream 方法获得一个流,因此能够称 menu 为 源
。它给流提供一个 元素序列
,接下来对流进行的一系列 数据处理操做
都会返回另外一个流:filter、 map、limit,这样它们就能够拼接成一条 流水线
。最后 collect 操做开始处理流水线,并返回结果。在调用 collect 以前没有任何结果产生,实际上根本就没有从 menu 里选择元素。能够这么说,在流水线里的方法调用都在排队等待,直到调用 collect。
粗略地说,流与集合之间的差别就在于何时进行计算。流是在概念上固定的数据结构,其元素则是按需计算的。例如,尽管质数有无穷多个,但咱们仅仅须要从流中提取须要的值,而这些值只会按需生成。与此相反,集合想要建立一个包含全部质数的集合,那会计算起来没完没了,由于总有新的质数要计算,这样咱们就不能从中取得须要的值。
和迭代器同样,流只能遍历一次,遍历完后咱们就能够说这个流被消费掉了。咱们能够从源再得到一个新的流来从新遍历。
用同一个流遍历 menu 两次会抛出异常
Stream<Dish> dishStream = menu.stream(); // 等同于 dishStream.forEach((Dish dish) -> System.out.println(dish)); dishStream.forEach(System.out::println); // java.lang.IllegalStateException: stream has already been operated upon or closed // 流已经被操做或关闭 dishStream.forEach(System.out::println);
集合须要咱们手动去作迭代,好比 for-each,这样被称为外部迭代。相反,流使用内部迭代。
集合:使用 for-each 循环外部迭代
List<String> dishNames = new ArrayList<>(); for (Dish dish : menu) { dishNames.add(dish.getName()); }
流:内部迭代
List<String> dishNames = menu .stream() .map(Dish::getName) .collect(Collectors.toList());
乍一看好像没有多大的区别,可是若是考虑到并行处理呢?若是使用外部迭代则须要咱们很痛苦地本身去处理并行,而用流则很是简单,只须要把 stream 换成 parallelStream。
流的使用通常包括三件事:
一个数据源来执行一个查询
一个中间操做链,造成一条流水线
一个终端操做,执行流水线并生成结果
以上即是流的一些基础知识,下一章会更加深刻理解流。
Java 8 实战 第四章 引入流 读书笔记
欢迎加入咖啡馆的春天(338147322)。