Java 8 中的 Stream 被定义为源的一系列元素,它们可以聚合操作。这里的源可以是Collections或Arrays,为源提供元素。Stream 会保持元素在源中的顺序,聚合操作或批量操作是在为每个元素进行相同的操作。java 8 中的 Stream 被设计为链式调用,它的大多数方法返回的是Stream本身。这种方式也被称为管道。
1. Java Stream VS Collection
我们平时在网站在线看视频,当开始观看时,视频文件的一部分会加载到我们的电脑,然后进行播放。你不需要在观看前下载完整的视频文件,这就是流。
Collection是内存中的数据结构,Collection中的每个元素在加载到Collection之前都完成计算的操作。而流的结构是固定的,它的元素是按需计算。这具有很大的优势,数据仅在用户需要时,才会提供相应的数据,这类似生产者 - 消费者的关系。
在Java中,java.util.Stream表示流,它可以进行一次或多少操作。流的操作分两种:一种是中间操作返回自身;另一种是返回一个明确类型值的终端操作,所有我们可以链式调用多个方法。流是在源上创建的,如java.util.Collection中的list、set(map不支持流操作)。流操作可以顺序执行,也可以并行执行。
流的特征:
不是一个数据结构
支持lambdas
不支持索引访问
可以很容易的作为列表和数组输出
支持延迟操作
可并行操作
2.创建流的不同方式
下面是常用的几种:
2.1 Stream.of(val1, val2, val3......)
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
stream.forEach(p -> System.out.println(p));
2.2 Stream.of(arrayOfElements)
Stream<Integer> stream = Stream.of(new Integer[]{1, 2, 3, 4, 5, 6});
stream.forEach(p -> System.out.println(p));
2.3 List.stream()
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
Stream<Integer> stream = list.stream();
stream.forEach(p -> System.out.println(p));
2.4 Stream.generate() or Stream.iterate()
Stream<Date> streamGenerate = Stream.generate(() -> {return new Date();});
streamGenerate.forEach(p -> System.out.println(p));
Stream<Integer> streamIterate = Stream.iterate(1, n -> n + 1);
streamIterate.forEach(p -> System.out.println(p));
2.5 String chars or String tokens
IntStream stream = "123456_abcdef".chars();
stream.forEach(p -> System.out.println(p));
// or
Stream<String> stream = Stream.of("A$B$C".split("\\$"));
stream.forEach(p -> System.out.println(p));
像Stream.Buider和一些中间方法返回流的,就不全列了。
3. 将流转为集合
流转集合其实是将流的所有数据全部放到集合中。
3.1 Stream 转为 List - Stream.collect(Collectors.toList())
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
Stream<Integer> stream = list.stream();
List<Integer> evenNumbers = stream.filter(i -> i % 2 == 0).collect(Collectors.toList());
System.out.println(evenNumbers);
3.2 Stream 转为 Array - Stream.toArray(EntryType::new)
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
Stream<Integer> stream = list.stream();
Integer[] evenNumbers = stream.filter(i -> i % 2 == 0).toArray(Integer[]::new);
Stream.of(evenNumbers).forEach(p -> System.out.println(p));
还有很多其它的方式将stream转为set、map,这些方式都在Collectors类中。
4.流的核心操作
流的接口中有一系列方法,这里列出一些重要的操作方法。
首先构建一个列表,下面例子都基于这个列表:
List<String> memberNames = new ArrayList<>();
memberNames.add("Amitabh");
memberNames.add("Shekhar");
memberNames.add("Aman");
memberNames.add("Rahul");
memberNames.add("Shahrukh");
memberNames.add("Salman");
memberNames.add("Yana");
memberNames.add("Lokesh");
4.1 中间方法
中间方法返回它自身,可以链式调用。
4.1.1 Stream.filter()
filter方法通过给定的判定去过滤Stream中的元素
memberNames.stream().filter(p -> p.startsWith("A")).forEach(System.out::println);
输出:
Amitabh
Aman
4.1.2 Stream.map()
将Stream中的每个元素应用map中给定的方法
memberNames.stream().filter(p -> p.startsWith("A")).map(String::toUpperCase).forEach(System.out::println);
输出:
AMITABH
AMAN
4.1.3 Stream.sorted()
返回Stream排序的视图,默认自然序,除非自定义排序。
memberNames.stream().sorted().map(String::toUpperCase).forEach(System.out::println);
注意:sorted方法仅仅创建Stream的排序视图,不会更改集合的原始顺序。
4.2 终端方法
终端方法返回具体的类型结果,而不是流本身
4.2.1 Stream.collect()
方法从Stream中接收元素保存到参数中指定的集合
List<String> list = memberNames.stream().sorted().map(String::toUpperCase).collect(Collectors.toList());
System.out.println(list);
结果:
[AMAN, AMITABH, LOKESH, RAHUL, SALMAN, SHAHRUKH, SHEKHAR, YANA]
4.2.2 Stream.match()
match有多种判定方式,结果返回一个boolean值
boolean m1 = memberNames.stream().anyMatch(p -> p.startsWith("A")); // 包含
boolean m2 = memberNames.stream().allMatch(p -> p.startsWith("A")); // 全部
boolean m3 = memberNames.stream().noneMatch(p -> p.startsWith("A")); // 不包含
System.out.println(m1);
System.out.println(m2);
System.out.println(m3);
结果:
true
false
false
4.2.3 Stream.count()
方法返回一个long数值,计数Stream中元素的数量
long count = memberNames.stream().filter(p -> p.startsWith("A")).count();
System.out.println(count); // 2
4.2.4 Stream.reduce()
方法会减少流的元素,返回最后值
memberNames.stream().reduce((s1, s2) -> s1 + "#" + s2).ifPresent(p -> System.out.println(p));
计算数字1-10的和
Integer[] numbers = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Stream.of(numbers).reduce((n1, n2) -> n1 + n2).ifPresent(p -> System.out.println(p));
5. Stream 短路操作
通常流会为集合中所有元素进行操作,但有时我们希望匹配某元素后停止后续操作。通常在外部循环,使用if-else即可。而在流的内部循环中,有明确的方法可以做到这点。
5.1 Stream.anyMatch()
一旦满足给定的判断条件,就立即返回,不再处理后续元素
boolean match = memberNames.stream().anyMatch(p -> p.startsWith("A"));
System.out.println(match); // true
5.2 Stream.findFirst()
它将从流中返回第一个元素,不处理后续元素。
String name = memberNames.stream().filter(p -> p.startsWith("S")).findFirst().get();
System.out.println(name); # Shekhar
6. Java Stream 的并行
多线程可以提升执行的性能,但编写健壮的多线程程序不易。Stream提供了健壮、容易编写的并行方法,只需要将顺序流stream替换成并行流parallelStream。上面所有例子中,你用parallelStream()替换stream(),就开启了并行。
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(i);
}
list.parallelStream().filter(p -> p % 2 == 0).forEach(p -> System.out.println(p));
结果
6
2
12
18
4
8
0
10
16
14
并行性可能带来不确定性和不期望的资源竞争。