Java Stream
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
Stream 的另外一大特点是,数据源本身可以是无限的。
1. Stream的定义
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection
操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
2. 流的构成
当我们使用流的时候,通常包括三个步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道
基本语法:
3. 流的生成
3.1 集合生成流
在 Java 8 中, 集合接口有两个方法来生成流:
stream() // 为集合创建串行流。
parallelStream() // 为集合创建并行流。
例如:
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();
3.2 数组生成流
通过 Arrays.stram()
,或通过Stream.of()
例如:
Stream<String> stream1 = Arrays.stream(new String[10]);
Stream<Integer> stream2 = Stream.of(1, 2, 3);
3.3 生成无限流
- 通过
Stream.iterate()
生成无限流
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
iterate.limit(10).forEach(System.out::println);
- 通过
Stream.generate()
Stream<Double> generate = Stream.generate(() -> Math.random());
generate.forEach(System.out::println);
4. 流的转换
流的转换有4种方式:
- distinct
- filter
- map
- flatMap
1. distinct
对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;
// 生成一个 0-10 的15个随机数
public static List<Integer> generateArray() {
return Stream.generate(() -> (int)(Math.random() * 10)).limit(15).collect(Collectors.toList());
}
// 去重
public void TestDistinct() {
List<Integer> collect = generateArray();
System.out.println(collect);
// 去重
List<Integer> collect1 = collect.stream().distinct().collect(Collectors.toList());
System.out.println(collect1);
}
2.filter
对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素
// 得到数组中的偶数
public void TestFilter() {
List<Integer> data = generateArray();
System.out.println(data);
// 去重
List<Integer> collect = data.stream().filter( x -> x % 2 == 0).collect(Collectors.toList());
System.out.println(collect);
}
3. map
对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。
// 计算数组中的每个元素的平方
public void TestMap() {
List<Integer> data = generateArray();
System.out.println(data);
List<Integer> collect = data.stream().map(x -> x * x).collect(Collectors.toList());
System.out.println(collect);
}
4. flatMap
对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。
5. peek
生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数
6. limit
对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;
7. skip
返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;
public void TestSkip() {
List<Integer> data = generateArray();
System.out.println(data);
List<Integer> collect = data.stream().skip(5).collect(Collectors.toList());
System.out.println(collect);
}
8. sorted
sorted方法将对原Stream进行排序,返回一个有序列的新Stream。sorterd有两种变体sorted(),sorted(Comparator),前者将默认使用Comparator进行排序,而后者接受一个自定义排序规则函数(Comparator),可按照意愿排序。
public void TestSorted() {
List<Integer> data = generateArray();
System.out.println(data);
// 默认排序
List<Integer> collect = data.stream().sorted().collect(Collectors.toList());
System.out.println(collect);
// 自定义排序:逆序排序
List<Integer> collect1 = data.stream().sorted((n1, n2) -> n2 - n1).collect(Collectors.toList());
System.out.println(collect1);
}
5. 流的聚合(Reduce)
汇聚操作(也称为折叠)接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List
对象。Stream
接口有一些通用的汇聚操作,比如reduce()
和collect()
;也有一些特定用途的汇聚操作,比如sum()
,max()
和count()
。
1. 可变汇聚
可变汇聚对应的只有一个方法:collect
,正如其名字显示的,它可以把Stream
中的要有元素收集到一个结果容器中(比如Collection)
int n=1;
List<Integer> data = Stream.generate(() -> n++).limit(5).collect(Collectors.toList());
System.out.println(data);
// map
Map<Integer, Integer> dmap = data.stream().collect(Collectors.toMap(x->x, x->x*x));
System.out.println(dmap);
// set
Set<Integer> dset = data.stream().collect(Collectors.toSet());
System.out.println(dset);
Reduce
这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce.
例如:
Integer sum = integers.reduce(0, (a, b) -> a+b);
Integer sum = integers.reduce(0, Integer::sum);
示例:
// 求数组的和
public void TestReduce() {
List<Integer> data = Arrays.asList(1,2,3);
System.out.println(data);
Integer sum = data.stream().reduce((s, y) -> s + y).get();
System.out.println("sum=" + sum);
// 第二种
Integer sum1 = data.stream().reduce(0, (s, y) -> s + y);
System.out.println("sum1=" + sum1);
}
// 求数组的乘积
public void TestReduce() {
List<Integer> data = Arrays.asList(1,2,3);
System.out.println(data);
Integer multi = data.stream().reduce((s, y) -> s * y).get();
System.out.println("multi=" + multi);
// 第二种
Integer multi1 = data.stream().reduce(1, (s, y) -> s * y);
System.out.println("multi=" + multi1);
}
其它汇聚
-
forEach
:替代for循环 -
count
:计算个数 -
allMatch
:是不是Stream中的所有元素都满足给定的匹配条件 -
anyMatch
:Stream中是否存在任何一个元素满足匹配条件 -
findFirst
: 返回Stream中的第一个元素,如果Stream为空,返回空Optional -
noneMatch
:是不是Stream中的所有元素都不满足给定的匹配条件 -
max
和min
:使用给定的比较器(Operator),返回Stream中的最大|最小值
2. 统计
一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。
常用方法:
getAverage()
getCount()
getMax()
getMin()
getSum()
public void TestStatic() {
List<Integer> data = generateArray();
System.out.println(data);
IntSummaryStatistics statistics = data.stream().mapToInt(x -> x).summaryStatistics();
double average = statistics.getAverage(); // 平局值
long count = statistics.getCount(); // 计数
int max = statistics.getMax(); // 最大值
int min = statistics.getMin(); // 最小值
long sum = statistics.getSum(); // 求和
}
3. 分组
使用Collectors.groupingBy()
生成的收集器,对元素做group操作时用到。
public void TestGroup() {
List<Integer> data = generateArray();
System.out.println(data);
// 按奇偶分组
Map<Boolean, List<Integer>> collect = data.stream().collect(Collectors.groupingBy(x -> x % 2 == 0, Collectors.toList()));
System.out.println(collect);
//按奇偶分组,并计数
Map<Boolean, Long> collect1 = data.stream().collect(Collectors.groupingBy(x -> x % 2 == 0, Collectors.counting()));
System.out.println(collect1);
//按奇偶分组,并求和
Map<Boolean, Integer> collect2 = data.stream().collect(Collectors.groupingBy(x -> x % 2 == 0, Collectors.summingInt(x -> x)));
System.out.println(collect2);
// 分组求平局值
Map<Boolean, Integer> collect3 = data.stream().collect(Collectors.groupingBy(x -> x % 2 == 0, Collectors.summingInt(x -> x)));
System.out.println(collect3);
}