前言
Java8 中新增的重要特性 Stream,我个人认为是非常方便的一套数据处理API。接下来,本人会以Stream的产生,处理,最终汇聚产出三大部分简单阐述这套API的魅力。希望对各位有所帮助
我对Java Stream的理解
Java的 Stream, 就像是流水线中货物,从一开始的原材料(Stream的生成),到中间流水线工人的加工(map,flatMap,filter),然后到最后的包装出品(reduce和collect)。经历每个步骤就能让从原材料到我们想看到的结果。
流水线只会往一个方向流,材料也加工了之后也没办法复原,所以Stream有一个很大的特性就是不可复用。同时还有 惰性执行 及 并行执行
代码实践
本部分会分为 Stream 生成,处理及终端输出操作三部分来做代码练习。
Stream生成
- 集合类 Collection 直接调用 stream() 生成流对象。
static List<Students> students = List.of(
new Students(0,"A",91,10),
new Students(1,"B",11,30),
new Students(0,"B",10,50),
new Students(1,"C",12,70),
new Students(1,"C",10.2,12),
new Students(0,"A",0.5,28),
new Students(0,"B",99,21),
new Students(0,"B",99,21)
);
// create Stream
Double average = students.stream().map(m -> m.getChi()).collect(Collectors.averagingDouble(Double::doubleValue));
System.out.println("1.average is " + average);
//find first
System.out.println("2. findFirst");
students.stream().findFirst().ifPresent(System.out::println);
//count == size
System.out.println("3. count()");
System.out.println(students.stream().count());//size()
//distinct()
System.out.println("4. distinct:");
students.stream().distinct().forEach(System.out::println); // dependence on equal()
System.out.println("5. limit()");
students.stream().limit(2).forEach(System.out::println);
输出:
Students{gender=0, classNum='A', chi=91.0, maths=10.0}
Students{gender=1, classNum='B', chi=11.0, maths=30.0}
Students{gender=0, classNum='B', chi=10.0, maths=50.0}
Students{gender=1, classNum='C', chi=12.0, maths=70.0}
Students{gender=1, classNum='C', chi=10.2, maths=12.0}
Students{gender=0, classNum='A', chi=0.5, maths=28.0}
Students{gender=0, classNum='B', chi=99.0, maths=21.0}
Students{gender=0, classNum='B', chi=99.0, maths=21.0}
1.average is 41.5875
2. findFirst
Students{gender=0, classNum='A', chi=91.0, maths=10.0}
3. count()
8
4. distinct:
Students{gender=0, classNum='A', chi=91.0, maths=10.0}
Students{gender=1, classNum='B', chi=11.0, maths=30.0}
Students{gender=0, classNum='B', chi=10.0, maths=50.0}
Students{gender=1, classNum='C', chi=12.0, maths=70.0}
Students{gender=1, classNum='C', chi=10.2, maths=12.0}
Students{gender=0, classNum='A', chi=0.5, maths=28.0}
Students{gender=0, classNum='B', chi=99.0, maths=21.0}
5. limit()
Students{gender=0, classNum='A', chi=91.0, maths=10.0}
Students{gender=1, classNum='B', chi=11.0, maths=30.0}
- 还有一下这些方式
//Stream 生成
System.out.println("======Stream 生成 S======");
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println);
Stream<String> stream1 = Stream.generate(() -> "a").limit(3);
stream1.forEach(System.out::println);
IntStream stream2 = "Hello,lalala".chars();
stream2.forEach(System.out::println);
Stream<String> stream3 = Pattern.compile("Hello").splitAsStream("KKHelloLL");
stream3.forEach(System.out::println);
Stream<Integer> streamIterated = Stream.iterate(1, n -> n + 2).limit(3);
streamIterated.forEach(System.out::println);
System.out.println("======Stream 生成 E======");
output:
======Stream 生成 S======
a
b
c
a
a
a
72
101
108
108
111
44
108
97
108
97
108
97
KK
LL
1
3
5
======Stream 生成 E======
虽然有很多中方式生成Stream对象,但生产上,我们主要还是面对集合类作操作。
Stream处理
以下为Stream中我经常会用到的操作。除此之外,在jdk8之后 还有其他API,如:takeWhile(),dropWhile(),mapMulti() …
//operation
Stream<String> stream = Stream.of("a", "b", "c");
// 每个字符串后面加上 mapped
stream.map(s -> s + ": mapped.").forEach(System.out::println);
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
//每个都乘以2
integerStream.map(s -> s *= 2).forEach(System.out::println);
stream = Stream.of("a", "b", "c");
//flatMap就是
stream.flatMap(s -> Stream.of(s + ": mapped.")).forEach(System.out::println);
//filter
stream = Stream.of("a", "b", "ac");
stream.filter(s -> s.contains("a")).forEach(System.out::println);
//concat
Stream<String> stringStream1 = Stream.of("c", "o", "n");
Stream<String> stringStream2 = Stream.of("c", "a", "t");
Stream.concat(stringStream1, stringStream2).forEach(System.out::print);
终端操作
Stream类自带的一些终端操作方法
Stream 类自带一些常用的终端操作方法,如count,max,toList, toArray, anyMatch,
// reduction 1. 预设的方法, 2. 自己基于 reduce 与 collect 来自定义的产出函数。
System.out.println("--- 预设的方法 ----");
System.out.println(strings.stream().count());
System.out.println(strings.stream().max((a, b) -> (int) a.charAt(1) - (int) b.charAt(1)).get());
IntSummaryStatistics intSummaryStatistics = strings.stream().mapToInt(s -> s.charAt(1)).summaryStatistics();
System.out.println(intSummaryStatistics);
//数据处理 Stream 的数据处理能力非常强大
System.out.printf("男生人数:%s \n", students.stream().filter(s -> s.getGender() == 0).count());
System.out.printf("总分最高:%s \n", students.stream().mapToDouble(s -> s.getChi() + s.getMaths()).max().getAsDouble());
output:
--- 预设的方法 ----
6
f6
IntSummaryStatistics{count=6, sum=309, min=49, average=51.500000, max=54}
男生人数:5
总分最高:120.0
reduce
如果jdk的操作方法不符合你的需求,你还可以使用reduce来处理。只需要传入一个累加器作为参数
//reduce 实现找出数学最高分的学生
students.stream().reduce((a,b) -> a.getMaths() > b.getMaths() ? a : b).ifPresent(System.out::println);
简单理解累加器就是,两个入参一个return
collect
如果简单的reduce无法满足你的需求则需要考虑自己编写 collector,
你先需要用Collector.of编写一个collector,三个参数分别是:
- 被输出新容器的提供者(supplier),这里需要return的是Map,则是Map的new方法,或者你自己写一个函数也可以
- 累加器(accumulator),这个累加器输出的是新的容器Map
- 合成器(combiner),通过前面两个步骤出来了一堆的 Map,但最终结果只会是一个方法他们合并在一起。
以下这个方法,其实就是按照班分类,算出不同班的数学总分。
Collector<Students,? , Map<String,Double>> collector = Collector.of(HashMap::new, (a, b) -> {
a.put(b.getClassNum(), b.getMaths());
},(a,b) ->{
b.forEach((key, value) -> a.put(key, a.getOrDefault(key, 0.0) + value));
return a;
});
Map<String, Double> collect = students.parallelStream().collect(collector);
collect.forEach((k,v) -> System.out.println(k + " " + v));
output
-------- Collector ------------
A 38.0
B 122.0
C 82.0
JDK同样提供很多collector给予我们使用,接下来的代码则是利用了groupingBy 达到同样的效果,且代码非常简洁。
Map<String, DoubleSummaryStatistics> collect1 = students.parallelStream().collect(Collectors.groupingBy(Students::getClassNum, Collectors.summarizingDouble(Students::getMaths)));
collect1.forEach((k,v) -> System.out.println(k + " " + v.getSum()));
特性
分别用以下几段代码简单体现出他这些特性。
- 不可复用
Stream<Integer> oneFive = Stream.iterate(1, i -> i + 1).limit(5);
oneFive.forEach(System.out::println);
oneFive.forEach(System.out::print);
则会报错
Exception in thread “main” java.lang.IllegalStateException: stream has already been operated upon or closed
- 惰性执行
//懒加载
List<String> strings = Arrays.asList("a1", "b2", "c3", "d4", "e5", "f6");
//这里只会输出一次。这属于 Stream的懒加载 懒加载取决于最后聚合哪一环。
Optional<String> first = strings.stream().map(s -> {
System.out.printf("current mapping is : %s ", s);
return s;
}).findFirst();
- 并行
并行则是调用.parallelStream() 方法实现。
结语
Java中的stream 是一套非常强大的数据处理 API, 通过简单的演示可以看出这套API 有多种处理方式,现在普遍公司面试时都会考这方面的知识。
本文有抛砖引玉之意,望读者 若有更多更好的理解 非常欢迎评论。
参考
[1]: https://www.baeldung.com/java-8-streams