Stream 操作一般包含一个 stream 源,0 个或多个中间操作,一个终结操作!
JDK 8 提供的常用的 stream 源:
Stream.iterate(T first, BinaryOperator( f)
Stream.iterate(T first, Predicate( test, BinaryOperator( f)
Stream.generate(Supplier( f)
JDK 8 提供的常用的中间操作方法:
filter(Predicate()
map(Function()
flatMap(Function(>)
JDK 8 提供的常用的终结操作方法:
forEach(Consumer( action)
利用 Stream 对数据集进行聚合或求和。
例 1: 对数列求和
// 使用 Stream
InStream.range(0, 10).filter(i->i%2==0).sum();
//[0,1,2,3,4,5,6,7,8,9]
// 不使用 Stream
int sum = 0;
for (int i=0; i<10; i++){
if (i%2==0) sum += i;
}
可见,使用 stream 的方式更简洁,数据处理的过程也更清晰。
方法 2 是典型的命令式编程,其中定义了一个 mutable 的累加变量,然后在循环中更新它的值,这种方式很不好,为什么呢?首先,这样的代码逻辑是无法进行并行(parallel)处理的,除非进行同步(synchronization),这样又会产生数据竞争的问题;其实,计算逻辑是基于个体元素而不是整个集合,对于复杂的场景,会难于应付。
Stream/Reduction 技术是基于函数式的编程思想,简单、灵活、可并行处理,操作高层的、抽象的数据单元。
Streams library 包含多种 Reduction 方法,比如:
Optional<T> reduce(BinaryOperator<T> op)
T reduce(T identity, BinaryOperator<T> op)
使用:
InStream.range(0, 10).reduce((i, j)-> i+j); // = OptionalInt[30]
InStream.range(0, 10).reduce(10, (i, j)-> i+j); // = 40
Stream.of("The", "girl", "is", "dangerous")
.map(s->s+" ")
.reduce(String::concat)
.get(); // "The girl is dangerous ! "
Reduction 不仅适用于整型和字符串,它可用于任何需要数据压缩的场景。
例如:找到个子最高的人
Comparator<Person> byHeight = Comparator.comparingInt(Person::getHeight);
BinaryOperator<Person> tallerOf = BinaryOperator.maxBy(byHeight);
Optional<Person> tallest = people.stream().reduce(tallerOf);
上面的 Comparator,BinaryOperator 都是 java 中的一些工具类,关键要知道自己需要什么样的逻辑,不一定使用它们,如下:
Optional<Person> tallest = people.stream()
.reduce((p1, p2)-> p1.getHeight()>p2.getHeight()?p1:p2);
Reduction 将数据序列缩减为一个值,比如求和,最小值,最大值。有时候,我们并不是希望得到一个值,而是希望将序列的内容重新组织到新的容器中,比如 List 或者 Map;或者希望得到多个值,这个时候就需要使用 Stream 的另一类方法 collect()。
例如,将 Stream 中的值转存到 List 中:
Stream.of(2,31,2,34,5,6,3,2,6,7,3,2,1)
.sorted()
.collect(Collectors.toList());
// [1, 2, 2, 2, 2, 3, 3, 5, 6, 6, 7, 31, 34]
collect() 方法有两种形式:
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);
combiner 使得整个 Reduction 过程可以并行处理: 将数据集分区,每个区产生一个累加的中间结果,然后合并这些中间结果,生成最终结果。
例如:
Stream.of("aa","bb", "cc", "dd").collect(StringBuilder::new,
StringBuilder::append,
StringBuilder::append) // aabbccdd
尽管 StringBuilder 是 mutable 字符串容器,但 collect 的实现并没有违法 accumulator pattern, 可以安全的并行化处理。在多线程中,每个线程有独立的 result container,它们的结果最终进行合并。
比如:
StringBuilder builder = new StringBuilder();
strings.stream().forEach(builder::append);
这才是应该避免的反面示例!
Collectors 提供了很多常用 Collector 的创建。
1 字符串拼接:
String concat = strings.stream()
.collect(Collectors.joining());
2 collect 数据到 set 容器
Set<String> uniqueStrings = strings.stream()
.collect(Collectors.toSet());
3 转存至 Collection
Stream.of("aa","bb", "cc", "dd")
.collect(Collectors.toCollection(()-> new ArrayList<String>()))
// [aa, bb, cc, dd]
4 转存至 Map
Stream.of("aa","bb", "cc", "dd")
.collect(Collectors.toMap((i)->i+"key", (i)->i+"value"))
// {aakey=aavalue, bbkey=bbvalue, cckey=ccvalue, ddkey=ddvalue}
5 分块
// 分为两大块: >5, <= 5
Stream.of(2,31,2,34,5,6,3,2,6,7,3,2,1)
.sorted()
.collect(Collectors.partitioningBy((i)->i>5))
// {false=[1, 2, 2, 2, 2, 3, 3, 5], true=[6, 6, 7, 31, 34]}
// 分为两大块,并对其粉笔求和
Stream.of(2,31,2,34,5,6,3,2,6,7,3,2,1)
.sorted()
.collect(Collectors.partitioningBy((i)->i>5, Collectors.summingInt((i)->i)))
// {false=20, true=84}
6 分组
// 对 3 取余作为 key,进行分组
Stream.of(2,31,2,34,5,6,3,2,6,7,3,2,1)
.sorted()
.collect(Collectors.groupingBy(i -> i%3))
// {0=[3, 3, 6, 6], 1=[1, 7, 31, 34], 2=[2, 2, 2, 2, 5]}
JDK 8 已经提供了各种功能丰富的接口方法,除此之外,还可以自定义 collector。
Collector 的接口:
public interface Collector<T, A, R> {
/ Return a function that creates a new empty result container */
Supplier<A> supplier();
/ Return a function that incorporates an element into a container /
BiConsumer<A, T> accumulator();
/** Return a function that merges two result containers */
BinaryOperator<A> combiner();
/** Return a function that converts the intermediate result container
into the final representation */
Function<A, R> finisher();
/** Special characteristics of this collector */
Set<Characteristics> characteristics();
}
实现 Collector:
new CollectorImpl<>(ArrayList::new,
List::add,
(left, right) ‑> { left.addAll(right); return left; },
CH_ID);