1、首先谈谈什么是流?
流是从支持数据处理操作的源中生成的元素序列。
拆分理解这句话:
- 元素序列:流提供了一个接口,可以访问特定元素类型的一组有序值;
- 源:被处理的数据,从有序集合生成流时会保留原有的顺序;由列表生成的流,其元素顺序和列表一致;
- 数据处理操作:与数据库操作类似,流操作可以顺序执行,也可以并行执行;
- 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一条大的流水线;
- 内部迭代:与使用迭代器显示迭代的集合不同,流的迭代操作是在背后进行的;
流有什么好处?或者我们为什么要使用流?
// 取出用户中大于24岁的人,并排序后输出
List<User> list = Arrays.asList(
new User("Dz", 20),
new User("Ym", 18),
new User("Json", 28),
new User("Dion", 25),
new User("Tom", 32),
new User("Kitty", 27)
);
// 传统的 Java 编码 start:
List<User> newList = new ArrayList<>();
for (User aList : list) {
if (aList.getAge() > 24) {
newList.add(aList);
}
}
Collections.sort(newList, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
});
for (User item : newList) {
System.out.println(item.getName() + ":" + item.getAge());
}
// 传统的 Java 编码 end
// 流操作
list.stream() //list.parallelStream() 可以并发执行
.filter(u -> u.getAge() > 24)
.sorted(Comparator.comparing(User::getAge))
.forEach(user -> System.out.println(user.getName() + ":" +user.getAge()));
流允许你以使用声明性的方式处理数据集合,而不是临时编写一个实现。具体好处如下(结合上述示例):
- 声明性 - 更简洁、易读;
- 可复合 - 更灵活;
- 可并行 - 性能更好;
2、流与集合对比
- 集合是一个内存中的数据结构,包含数据结构中目前所有的值;
- 而流是概念上固定的数据结构,其元素是按需加载的;
- 流只能被遍历一次,遍历完之后就被消费掉了;
- 遍历数据的方式也是他们一个关键区别:
- Collection 需要用户去编码迭代,这称为外部迭代;
- Streams 库使用内部迭代,不需要去编码,它内部帮忙把这些事情做了;
- 集合主要是访问数据,而流的目的在于数据计算;
3、流操作
流主要两种类别的操作:中间操作,终端操作。
中间操作形成流水线、而终端操作则是从流水线中生成结果。
使用流则包含三个要素:
- 数据源;
- 中间操作链;
- 终端操作;
4、使用流
4.1 筛选: (.filter 筛选比较用的比较多)
- filter():该操作接受一个谓词,筛选出符合谓词的元素的流;
- distinct():返回一个元素各异的流(根据流所生成元素的 hashCode 和 equals 方法实现);
- limit(n):截短流,返回一个不会超过给定长度的流,若流是有序的,最多返回前 n 个原色;
- skip(n):跳过元素,返回一个丢弃掉前 n 个元素的流,如果流中不足 n 个则返回一个空流;
4.2 映射: (map 将一个实体映射成一个新的元素,如获取一个实体的一项属性)
- map():对流中每个一个元素应用函数,并将其映射成一个新的元素;
List<String> words = Arrays.asList("Java", "Lambda", "Work", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
- flatMap(Arrays::stream):流的扁平化,把所有的流连接 起来成为一个流;
// 列 words 出里面各不相同的字符
// .map(word -> word.split("")) 返回的是 Stream<String[]>,不是我们想要的Stream<String>
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.distinct()
.collect(toList());
List<String> uniqueCharacters =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
// 所有使用 map(Arrays::stream) 时生成的单个流都被合并起来,即扁平化为一个流
4.3 查找与匹配: (查找满足条件的一项)
Stream API 通过 allMatch、anyMatch、noneMatch、findFirst 和 findAny 方法提供了这样的工具。
Optional<Dish> dish =
menu.stream()
.filter(Dish::isVegetarian)
.findAny()
.ifPresent(d -> System.out.println(d.getName());
// 介绍如下文所示
Optional简介
Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在。
- isPresent()将在 Optional 包含值的时候返回 true, 否则返回 false;
- ifPresent(Consumer<T> block)会在值存在的时候执行给定的代码块;
- T get()会在值存在时返回值,否则抛出一个 NoSuchElement 异常;
- T orElse(T other)会在值存在时返回值,否则返回一个默认值;
4.4 归约:(将流归约成一个值 .reduce())
- 对流求和;
- 流中最大值、最小值问题;
- 求流中元素个数;
.reduce()
- 一个初始值,这里是0;
- 一个 BinaryOperator<T>来将两个元素结合起来产生一个新值,这里我们用的是 lambda (a, b) -> a + b;
无初始值:reduce 还有一个重载的变体,它不接受初始值,但是会返回一个 Optional 对象;
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
//为什么它返回一个Optional<Integer>呢?考虑流中没有任何元素的情况。
//reduce操作无法返回其和,因为它没有初始值。
//当然你也可以利用 Optional 自带的几个方法:
int sum = numbers.stream().reduce((a, b) -> (a + b)).orElse(0);
5 数值流 -- 原始类型流特化
- IntStream;
- DoubleStream;
- LongStream;
6、总结
- Streams API 可以表达复杂的数据处理查询;
- 你可以使用 filter、distinct、skip 和 limit 对流做筛选和切片;
- 你可以使用 map 和 flatMap 提取或转换流中的元素;
- 你可以使用 findFirst 和 findAny 方法查找流中的元素。你可以用 allMatch 、noneMatch 和 anyMatch 方法让流匹配给定的谓词;
- 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流;
- 你可以利用 reduce 方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大、最小元素;
- filter 和 map 等操作是无状态的,它们并不存储任何状态。reduce 等操作要存储状态才能计算出一个值。sorted 和 distinct 等操作也要存储状态,因为它们需要把流中的所
- 有元素缓存起来才能返回一个新的流。这种操作称为有状态操作;
- 流有三种基本的原始类型特化:IntStream、DoubleStream 和 LongStream。它们的操作也有相应的特化;
- 流不仅可以从集合创建,也可从值、数组、文件以及 iterate 与 generate 等特定方法 创建;