Java 8 使用 Stream,Collector,Collectors 编程

文章目录

  • 1、Stream
  • 2、Collector
    • Reduction
    • Collect
  • 3、Collectors
    • 自定义 Collector

参考资料
https://developer.ibm.com/articles/j-java-streams-2-brian-goetz/
https://developer.ibm.com/articles/j-java-streams-1-brian-goetz/

1、Stream

Stream 操作一般包含一个 stream 源,0 个或多个中间操作,一个终结操作!

JDK 8 提供的常用的 stream 源:

  • Collection.stream()
  • Stream.of(T…)
  • Stream.of(T[])
  • Stream.empty()
  • Stream.iterate(T first, BinaryOperator( f)
  • Stream.iterate(T first, Predicate( test, BinaryOperator( f)
  • Stream.generate(Supplier( f)
  • IntStream.range(lower, upper)
  • IntStream.rangeClosed(lower, upper)
  • BufferedReader.lines()
  • BitSet.stream()
  • CharSequence.chars()

JDK 8 提供的常用的中间操作方法:

  • filter(Predicate()
  • map(Function()
  • flatMap(Function(>)
  • distinct()
  • sorted()
  • limit(long)
  • skip(long)

JDK 8 提供的常用的终结操作方法:

  • forEach(Consumer( action)
  • toArray()
  • reduce(…)
  • collect(…)
  • count()

2、Collector

利用 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 技术是基于函数式的编程思想,简单、灵活、可并行处理,操作高层的、抽象的数据单元。

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);

Collect

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);
  • supplier : 提供一个结果容器
  • accumulator: 将结果放在 supplier 产生的结果容器中
  • combiner : 合并两个结果容器

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);

这才是应该避免的反面示例!

3、Collectors

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]}

自定义 Collector

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);

你可能感兴趣的:(Java)