Java函数式编程(三)Stream
3 函数式编程特性的应用
3.2 Stream
Stream是用函数式编程方式在集合类上进行复杂操作的工具,它提供了几个重要的函数编程的特性:
- 链式构造
- 外部迭代到内部迭代
- 惰性求值方法
- 高阶函数
3.2.1 类结构
3.2.1.1 BaseSream
最顶层接口,提供并行,顺序等相关操作
修饰符和返回值 | 方法 | 描述 |
---|---|---|
void | close() | 关闭此流,导致调用此流管道的所有关闭处理程序。 |
boolean | isParallel() | 返回如果要执行终端操作,该流是否将并行执行。 |
Iterator | iterator() | 返回此流元素的迭代器。 |
S | onClose(Runnable closeHandler) | 返回具有附加关闭处理程序的等效流。 |
S | parallel() | 返回并行的等效流。 |
S | sequential() | 返回顺序的等效流。 |
Spliterator<T> | spliterator() | 返回此流元素的拆分器。 |
S | unordered() | 返回未排序的等效流。 |
3.2.1.2 Stream
用来处理引用类型的流通用接口
修饰符和返回值 | 方法 | 描述 |
---|---|---|
boolean | allMatch(Predicate<? super T> predicate) | 返回此流的所有元素是否与提供的谓词匹配。 |
boolean | anyMatch(Predicate<? super T> predicate) | 返回此流的任何元素是否与提供的谓词匹配。 |
static <T> Stream.Builder<T> | builder() | 返回流的生成器。 |
<R,A> R | collect(Collector<? super T,A,R> collector) | 使用收集器对此流的元素执行可变缩减操作。 |
<R> R | collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) | 对该流的元素执行可变缩减操作。 |
static <T> Stream<T> | concat(Stream<? extends T> a, Stream<? extends T> b) | 创建一个延迟连接流,其元素是第一个流的所有元素,然后是第二个流的全部元素。 |
long | count() | 返回此流中元素的计数。 |
Stream<T> | distinct() | 返回由该流的不同元素(根据Object.equals(Object))组成的流。 |
static <T> Stream<T> | empty() | 返回空序列流。 |
Stream<T> | filter(Predicate<? super T> predicate) | 返回由该流中与给定谓词匹配的元素组成的流。 |
Optional | findAny() | 返回描述流中某些元素的可选值,如果流为空,则返回空Optional。 |
Optional<T> | findFirst() | 返回描述此流的第一个元素的可选值,如果流为空,则返回空Optional。 |
<R> Stream<R> | flatMap(Function<? super T,? extends Stream<? extends R>> mapper) | 返回一个流,该流由将该流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果组成。 |
DoubleStream | flatMapToDouble(Function<? super T,? extends DoubleStream> mapper) | 返回一个DoubleStream,该DoubleStream由将该流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果组成。 |
IntStream | flatMapToInt(Function<? super T,? extends IntStream> mapper) | 返回一个IntStream,其中包含用映射流的内容替换此流的每个元素的结果,该映射流是通过将提供的映射函数应用于每个元素而生成的。 |
LongStream | flatMapToLong(Function<? super T,? extends LongStream> mapper) | 返回一个LongStream,该LongStream由将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果组成。 |
void | forEach(Consumer<? super T> action) | 为此流的每个元素执行操作。 |
void | forEachOrdered(Consumer<? super T> action) | 如果流具有定义的相遇顺序,则按流的相遇顺序为流的每个元素执行操作。 |
static <T> Stream<T> | generate(Supplier<T> s) | 返回无限顺序无序流,其中每个元素由提供的供应器生成。 |
static <T> Stream<T> | iterate(T seed, UnaryOperator<T> f) | 返回通过将函数f迭代应用于初始元素seed而生成的无限顺序有序流,生成由种子、f(seed)、f(f(seed))等组成的流。 |
Stream<T> | limit(long maxSize) | 返回由该流的元素组成的流,该流被截断为长度不超过maxSize。 |
<R> Stream<R> | map(Function<? super T,? extends R> mapper) | 返回一个由将给定函数应用于流元素的结果组成的流。 |
DoubleStream | mapToDouble(ToDoubleFunction<? super T> mapper) | 返回由将给定函数应用于流元素的结果组成的DoubleStream。 |
IntStream | mapToInt(ToIntFunction<? super T> mapper) | 返回一个IntStream,其中包含将给定函数应用于该流元素的结果。 |
LongStream | mapToLong(ToLongFunction<? super T> mapper) | 返回由将给定函数应用于流元素的结果组成的LongStream。 |
Optional<T> | max(Comparator<? super T> comparator) | 根据提供的Comparator返回此流的最大元素。 |
Optional<T> | min(Comparator<? super T> comparator) | 根据提供的比较器返回此流的最小元素。 |
boolean | noneMatch(Predicate<? super T> predicate) | 返回此流的元素是否与提供的谓词匹配。 |
static <T> Stream<T> | of(T… values) | 返回元素为指定值的顺序有序流。 |
static <T> Stream<T> | of(T t) | 返回包含单个元素的顺序流。 |
Stream<T> | peek(Consumer<? super T> action) | 返回由该流的元素组成的流,当元素从结果流中被消耗时,对每个元素执行提供的操作。 |
Optional<T> | reduce(BinaryOperator<T> accumulator) | 使用关联累加函数对该流的元素执行缩减,并返回描述缩减值(如果有)的Optional。 |
T | reduce(T identity, BinaryOperator<T> accumulator) | 使用提供的标识值和关联累加函数对该流的元素执行缩减,并返回缩减值。 |
-<\U> U | reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) | 使用提供的标识、累积和组合函数对该流的元素执行缩减。 |
Stream<T> | skip(long n) | 在丢弃流的前n个元素之后,返回由该流的剩余元素组成的流。 |
Stream<T> | sorted() | 返回由该流的元素组成的流,按自然顺序排序。 |
Stream<T> | sorted(Comparator<? super T> comparator) | 返回由该流的元素组成的流,根据提供的比较器进行排序。 |
Object[] | toArray() | 返回包含此流元素的数组。 |
<A> A[] | toArray(IntFunction<A[]> generator) | 返回包含此流元素的数组,使用提供的生成器函数分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。 |
列举几个Stream的例子:
package com.qupeng.fp.lambda;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class TestStream {
public static void main(String[] args) {
List<String> strList = Stream.of("a", "b", "c").collect(() -> new ArrayList<String>(), (list, str) -> {
list.add(str);
}, (list, list2) -> {
list.addAll(list2);
});
System.out.println(strList); // output is: [a, b, c]
Stream.builder().add("b").add("c").add("a").build().parallel().forEach(str -> {
System.out.print(str); // output is not in decided sequence, maybe cba
});
Stream.builder().add("b").add("c").add("a").build().parallel().forEachOrdered(str -> {
System.out.print(str); // output is in fixed sequence: bca
});
// print a infinitely aaaaaa...
Stream.generate(() -> "a").forEach(str -> System.out.println(str));
/* output is:
peek: a, consume:a
peek: b, consume:b
peek: c, consume:c
*/
Stream.of("a", "b", "c").peek(str -> System.out.print("peek: " + str + ", ")).forEach(str -> System.out.println("consume:" + str)); // output is : *aa*bb*cc
String[] strs = Stream.of("a", "b", "c").toArray(String[]::new);
System.out.println(strs);
}
}
3.2.1.3 基本数值类型Stream
在数值计算中,基本类型的运行效率明显高于装箱类型,所以在Stream中,Java提供了3中基本类型的stream:IntStream,LongStream,DoubleStream,用来支持3中数值计算中用的最多的基本类型。在数值计算中使用他们,能明显提升系统性能。
这3种基本类型Stream对应3个不同于Stream的接口,而且接口中提供了新的方法,例如summayStatistics。
IntSream
IntSream中特有的方法:
修饰符和返回值 | 方法 | 描述 |
---|---|---|
DoubleStream | asDoubleStream() | 返回由该流的元素组成的DoubleStream,转换为double。 |
LongStream | asLongStream() | 返回由该流的元素组成的LongStream,转换为long |
OptionalDouble | average() | 返回描述此流元素算术平均值的OptionalDouble,如果此流为空,则返回空可选值。 |
Stream<Integer> | boxed() | 返回由该流的元素组成的流,每个元素被装箱为Integer。 |
DoubleStream | mapToDouble(IntToDoubleFunction mapper) | 返回由将给定函数应用于流元素的结果组成的DoubleStream。 |
LongStream | mapToLong(IntToLongFunction mapper) | 返回由将给定函数应用于流元素的结果组成的LongStream。 |
<U> Stream<U> | mapToObj(IntFunction<? extends U> mapper) | 返回一个对象值流,该流包含将给定函数应用于该流元素的结果。 |
static IntStream | range(int startInclusive, int endExclusive) | 返回一个顺序有序的IntStream,从startInclusive(包含)到endExclusive(排除),递增1步。 |
static IntStream | rangeClosed(int startInclusive, int endInclusive) | 返回一个顺序有序的IntStream,从startInclusive(包含)到EndInclusible(包含),递增1步。 |
Spliterator.OfInt | spliterator() | 返回此流元素的拆分器。 |
int | sum() | 返回此流中元素的总和。 |
IntSummaryStatistics | summaryStatistics() | 返回一个IntSummaryStatistics,描述有关此流元素的各种摘要数据。 |
几个简单的例子:
package com.qupeng.fp.lambda;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;
public class TestIntStream {
public static void main(String[] args) {
double doubleValue = IntStream.of(1, 2, 3, 5).average().getAsDouble();
System.out.println(doubleValue);
IntStream.of(1, 2, 3, 5).boxed();
IntStream.of(1, 2, 3, 5).mapToObj(String::valueOf);
IntStream.range(5, 10).forEach(System.out::print); // 56789
IntStream.rangeClosed(5, 10).forEach(System.out::print); // 5678910
IntStream.rangeClosed(5, 10).iterator().forEachRemaining((IntConsumer)System.out::print);
IntStream.rangeClosed(5, 100).spliterator().forEachRemaining((IntConsumer)System.out::println);
IntStream.rangeClosed(5, 10).summaryStatistics().getSum();
IntStream.rangeClosed(5, 10).summaryStatistics().getAverage();
IntStream.rangeClosed(5, 10).summaryStatistics().getCount();
IntStream.rangeClosed(5, 10).summaryStatistics().getMax();
IntStream.rangeClosed(5, 10).summaryStatistics().getMin();
}
}
LongSream
DoubleSream
3.2.1.4 StreamSupport
用来生成不同流实例的支持类
修饰符和返回值 | 方法 | 描述 |
---|---|---|
static DoubleStream | doubleStream(Spliterator.OfDouble spliterator, boolean parallel) | 从Spliterator.OfDouble创建新的顺序或并行DoubleStream。 |
static DoubleStream | doubleStream(Supplier<? extends Spliterator.OfDouble> supplier, int characteristics, boolean parallel) | 从Spliterator.OfDouble的supplier创建新的顺序或并行DoubleStream。 |
static IntStream | intStream(Spliterator.OfInt spliterator, boolean parallel) | 从Spliterator.OfDouble创建新的顺序或并行IntStream。 |
static IntStream | intStream(Supplier<? extends Spliterator.OfInt> supplier, int characteristics, boolean parallel) | 从Spliterator.OfDouble的supplier创建新的顺序或并行IntStream。 |
static LongStream | longStream(Spliterator.OfLong spliterator, boolean parallel) | 从Spliterator.OfDouble创建新的顺序或并行LongStream。 |
static LongStream | longStream(Supplier<? extends Spliterator.OfLong> supplier, int characteristics, boolean parallel) | 从Spliterator.OfDouble的supplier创建新的顺序或并行LongStream。 |
static Stream | stream(Spliterator spliterator, boolean parallel) | 从拆分器创建新的顺序或并行流。 |
static Stream | stream(Supplier<? extends Spliterator> supplier, int characteristics, boolean parallel) | 从Spliterator的Supplier创建新的顺序或并行流。 |
3.2.1.5 Interface Spliterator
Spliterator是Java 8中加入的一个新接口,这个名字代表“可拆分迭代器”(splitable iterator)。和Iterator一样,Spliterator也用于遍历数据源中的元素,但它是为了并行执行而设计的。Java 8已经为集合框架中包含的所有数据结构提供了一个默认的Spliterator实现。集合实现了Spliterator接口,接口提供了一个spliterator方法。
3.2.1.6 Interface Collector<T,A,R>
Collector定义了一种可变归约(Reduction)操作
,将输入元素累积到可变结果容器
中,在处理完所有输入元素后,可选择将累积结果转换为最终表示。
归约操作可以顺序或并行执行。
可变归约操作的示例包括:
- 将元素累积到集合中;
- 使用 StringBuilder 连接字符串;
- 计算总和、最小值、最大值或平均值等元素的摘要信息;
- 计算“数据透视表”摘要,例如“卖方的最大交易价值”等。
Collectors 类提供了许多常见的可变缩减的实现。
在讨论接口具体的方法之前,先来看看泛型参数描述:
<T> 规约操作的输入元素类型
<A> 规约操作的可变结果容器类型
<R> 规约操作的结果类型
收集器由四个核心方法指定,这些函数协同工作以将条目累积到可变结果容器中,并可选择对结果执行最终转换。他们是:
- Supplier<A> supplier()
创建一个新的可变结果容器 - BiConsumer<A, T> accumulator()
将新数据元素合并到结果容器中 - BinaryOperator<A> combiner()
将两个结果容器合并为一个 - Function<A, R> finisher()
对容器执行可选的最终转换
收集器还具有一组特征,例如 Collector.Characteristics.CONCURRENT,它们提供了减少实现可以用来提供更好性能的提示。
Characteristics代表着可以在优化规约操作的一些特性。收集器可以指明这些特性。
- CONCURRENT特性:代表这个收集器支持并发,这里并发指可以支持并发(多线程)的对同一个result container进行“累加操作”。如果一个收集器具有CONCURRENT特性,但是没有UNORDERED特性,就意味着:这个收集器只会在应用到一个无序数据源(如Set)时,才会被并发的执行收集过程。
- UNORDERED特性:代表这个收集器在收集时不会保留它遇到的元素的顺序,如果一个收集器的结果容器(result container)是一个Set这样的无序容器,那么应该给你的结果容器设置UNORDERED这个特性。
- IDENTITY_FINISH特性:代表这个收集器的finisher是个恒等函数.也就代表着如果设置了这个特性,泛型类型A转换为R不需要检查,不需要额外的转换逻辑,finisher函数也不会被调用。
修饰符和返回值 | 方法 | 描述 |
---|---|---|
BiConsumer<A,T> | accumulator() | 将值折叠到可变结果容器中的函数。 |
Set<Collector.Characteristics> | characteristics() | 返回一组 Collector.Characteristics,指示此收集器的特征。 |
BinaryOperator<A> | combiner() | 接受两个部分结果并将它们合并的函数。 |
Function<A,R> | finisher() | 执行从中间累加类型A到最终结果类型R的最终转换。 |
static <T,A,R> Collector<T,A,R> | of(Supplier<A> supplier, BiConsumer<A,T> accumulator, BinaryOperator<A> combiner, Function<A,R> finisher, Collector.Characteristics… characteristics) | 返回由给定供应商、累加器、组合器和整理器函数描述的新收集器。 |
static <T,R> Collector<T,R,R> | of(Supplier<R> supplier, BiConsumer<R,T> accumulator, BinaryOperator<R> combiner, Collector.Characteristics… characteristics) | 返回一个新的 Collector 由给定的供应商、累加器和组合器函数描述。 |
Supplier<A> | supplier() | 创建并返回一个新的可变结果容器的函数。 |
简单的示例:
package com.qupeng.fp.stream.collector;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
public class TestCollector {
public static void main(String[] args) {
// 终结类型为String,可变容器类型是List,所以必须调用finisher方法,并且不能设置Collector.Characteristics.IDENTITY_FINISH属性,否则会抛异常
String result = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(new MyCollector());
System.out.println(result);
// 终结类型为List<Integer>,可变容器类型是List<Integer>,所以可以设置或不设置Collector.Characteristics.IDENTITY_FINISH属性。不设置会调用finisher,不设置的话不会调用finisher
List<Integer> result1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collector.of(ArrayList<Integer>::new, (lst, integer) -> lst.add(integer), (lst, lst2) -> {
lst.addAll(lst2);
return lst;
}, list -> {
List<Integer> list1 = new LinkedList<>();
Collections.reverse(list);
list1.addAll(list);
return list1;
}, Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH));
System.out.println(result1);
}
static class MyCollector implements Collector<Integer, List, String> {
@Override
public Supplier<List> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List, Integer> accumulator() {
return (lst, integer) -> lst.add(integer);
}
@Override
public BinaryOperator<List> combiner() {
return (lst, lst2) -> {
lst.addAll(lst2);
return lst;
};
}
@Override
public Function<List, String> finisher() {
return list -> list.toString();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED));
}
}
}
3.2.1.7 Collectors
实现各种有用的归约操作的 Collector 的实现,例如将元素累积到集合中,根据各种标准汇总元素等。
修饰符和返回值 | 方法 | 描述 |
---|---|---|
static <T> Collector<T,?,Double> | averagingDouble(ToDoubleFunction<? super T> mapper) | 返回一个收集器,它产生应用于输入元素的双值函数的算术平均值。 |
static <T> Collector<T,?,Double> | averagingInt(ToIntFunction<? super T> mapper) | 返回一个收集器,它产生应用于输入元素的整数值函数的算术平均值。 |
static <T> Collector<T,?,Double> | averagingLong(ToLongFunction<? super T> mapper) | 返回一个收集器,它产生应用于输入元素的长值函数的算术平均值。 |
static <T,A,R,RR> Collector<T,A,RR> | collectingAndThen(Collector<T,A,R>downstream, Function<R,RR> finisher) | 调整收集器以执行附加的整理转换。 |
static <T> Collector<T,?,Long> | counting() | 返回一个接受类型为 T 的元素的收集器,用于计算输入元素的数量。 |
static <T,K> Collector<T,?,Map<K,List<T>>> | groupingBy(Function<? super T,? extends K> classifier) | 返回一个对输入实现“分组依据”操作的收集器T 类型的元素,根据分类函数对元素进行分组,并在 Map 中返回结果。 |
static <T,K,A,D> Collector<T,?,Map<K,D>> | groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream) | 返回对类型 T 的输入元素执行级联“分组依据”操作的收集器,根据分类函数对元素进行分组,然后使用指定的下游收集器对与给定键关联的值执行归约操作。 |
static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M> | groupingBy(Function<? super T,? extends K> classifier, Supplier mapFactory, Collector<? super T,A,D> downstream) | 返回一个收集器,对类型 T 的输入元素执行级联“分组依据”操作,根据分类函数对元素进行分组,然后使用指定的下游收集器对与给定键关联的值执行归约操作。 |
static <T,K> Collector<T,?,ConcurrentMap<K,List>> | groupingByConcurrent(Function<? super T,? extends K> classifier) | 返回一个并发收集器,对 T 类型的输入元素执行“分组”操作,根据分类函数对元素进行分组。 |
static <T,K,A,D> Collector<T,?,ConcurrentMap<K,D>> | groupingByConcurrent(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream) | 返回一个并发收集器,对 T 类型的输入元素执行级联“分组依据”操作,根据分类函数对元素进行分组,然后使用指定的下游收集器对与给定键关联的值执行归约操作。 |
static <T,K,A,D,M extends ConcurrentMap<K,D>> Collector<T,?,M> | groupingByConcurrent(Function<? super T,? extends K> classifier, Supplier mapFactory, Collector<? super T,A,D> downstream) | 返回实现级联“对类型 T 的输入元素进行 group by" 操作,根据分类函数对元素进行分组,然后使用指定的下游收集器对与给定键关联的值执行归约操作。 |
static Collector<CharSequence,?,String> | joining() | 返回一个收集器,它将输入元素按照遇到的顺序连接成一个字符串。 |
static Collector<CharSequence,?,String> | joining(CharSequence delimiter) | 返回一个收集器,它按照遇到的顺序连接输入元素,由指定的分隔符分隔。 |
static Collector<CharSequence,?,String> | joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) | 返回一个收集器,它连接输入元素,由指定的分隔符分隔,具有指定的前缀和后缀,按照遇到的顺序。 |
static <T,U,A,R> Collector<T,?,R> | mapping(Function<? super T,? extends U> mapper, Collector<? super U,A,R> 下游) | 适配一个 Collector 接受通过在累加之前对每个输入元素应用映射函数,将 U 类型的元素转换为一个接受 T 类型的元素。 |
static <T> Collector<T,?,Optional<T>> | maxBy(Comparator<? super T> comparator) | 返回一个收集器,它根据给定的比较器生成最大元素,描述为 Optional<T>。 |
static <T> Collector<T,?,Optional<T>> | minBy(Comparator<? super T> comparator) | 返回一个收集器,它根据给定的比较器生成最小元素,描述为 Optional<T>。 |
static <T> Collector<T,?,Map<Boolean,List<T>>> | partitioningBy(Predicate<? super T> predicate) | 返回一个 Collector,根据 Predicate 对输入元素进行分区,并将它们组织成一个地图<布尔值,列表<T>>。 |
static <T,D,A> Collector<T,?,Map<Boolean,D>> | partitioningBy(Predicate<? super T> predicate, Collector<? super T,A,D> downstream) | 返回一个 Collector,它根据 Predicate 对输入元素进行分区,根据另一个 Collector 对每个分区中的值进行归约,并将它们组织成 Map<Boolean, D> ,其值是下游归约的结果。 |
static <T> Collector<T,?,Optional<T>> | reducing(BinaryOperator<T> op) | 一个收集器,它在指定的 BinaryOperator 下执行其输入元素的缩减。 |
static <T> Collector<T,?,T> | reducing(T identity, BinaryOperator<T> op) | 一个收集器,它使用提供的标识在指定的 BinaryOperator 下执行其输入元素的缩减。 |
static <T,U> Collector<T,?,U> | reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op) | 一个收集器,它在指定的映射函数和 BinaryOperator 下执行其输入元素的缩减。 |
static <T> Collector<T,?,DoubleSummaryStatistics> | summarizingDouble(ToDoubleFunction<? super T> mapper) | 一个收集器,它在指定的映射函数和 BinaryOperator 下执行其输入元素的缩减。 |
static <T> Collector<T,?,IntSummaryStatistics> | summarizingInt(ToIntFunction<? super T> mapper) | 一个收集器,它将产生 int 的映射函数应用于每个输入元素,并返回结果值的汇总统计信息。 |
static <T> Collector<T,?,LongSummaryStatistics> | summarizingLong(ToLongFunction<? super T> mapper) | 一个收集器,它将一个长期生成的映射函数应用于每个输入元素,并返回结果值的汇总统计信息。 |
static <T> Collector<T,?,Double> | summingDouble(ToDoubleFunction<? super T> mapper) | 一个收集器,它产生应用于输入元素的双值函数的总和。 |
static <T> Collector<T,?,Integer> | summingInt(ToIntFunction<? super T> mapper) | 一个收集器,它产生应用于输入元素的整数值函数的总和。 |
static <T> Collector<T,?,Long> | summingLong(ToLongFunction<? super T> mapper) | 一个收集器,它产生应用于输入元素的长值函数的总和。 |
static <T,C extends Collection<T>> Collector<T,?,C> | toCollection(Supplier collectionFactory) | 一个收集器,它将输入元素按遇到的顺序累积到一个新的集合中。 |
static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> | toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) | 一个并发收集器,它将元素累积到一个 ConcurrentMap 中,其键和值是将提供的映射函数应用于输入元素的结果。 |
static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> | toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction) | 一个并发收集器,它将元素累积到一个 ConcurrentMap 中,其键和值是将提供的映射函数应用于输入元素的结果。 |
static <T,K,U,M extends ConcurrentMap<K,U>> Collector<T,?,M> | toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) | 一个并发收集器,它将元素累积到一个 ConcurrentMap 中,其键和值是将提供的映射函数应用于输入元素的结果。如果映射的键包含重复项(根据 Object.equals(Object)),则将值映射函数应用于每个相等的元素,并使用提供的合并函数合并结果。 |
static <T> Collector<T,?,List<T>> | toList() | 将输入元素累积到新列表中的收集器。 |
static <T,K,U> Collector<T,?,Map<K,U>> | toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) | 将元素累积到 Map 中的 Collector,其键和值是将提供的映射函数应用于输入元素的结果。 |
static <T,K,U> Collector<T,?,Map<K,U>> | toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction) | 将元素累积到 Map 中的 Collector,其键和值是将提供的映射函数应用于输入元素的结果。如果映射的键包含重复项(根据 Object.equals(Object)),则将值映射函数应用于每个相等的元素,并使用提供的合并函数合并结果。 |
static <T,K,U,M extends Map<K,U>> Collector<T,?,M> | toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) | 将元素累积到 Map 中的 Collector,其键和值是将提供的映射函数应用于输入元素的结果。 |
static <T> Collector<T,?,Set<T>> | toSet() | 一个收集器,将输入元素累积到一个新的集合中。 |
简单示例:
package com.qupeng.fp.stream.collector;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestCollector {
public static void main(String[] args) {
// 转换为整型后求平均值
Double result1 = Stream.of("1", "2", "3").collect(Collectors.averagingInt(str -> Integer.valueOf(str)));
System.out.println(result1);
// 转换为双精度后求平均值
Double result2 = Stream.of("1.9", "2.9", "3.9").collect(Collectors.averagingDouble(str -> Double.valueOf(str)));
System.out.println(result2);
// 转换为长整型后求平均值
Double result3 = Stream.of("1", "2", "3").collect(Collectors.averagingLong(str -> Long.valueOf(str)));
System.out.println(result3);
// 先收集,再转换
String averageValue = Stream.of("1", "2", "3").collect(Collectors.collectingAndThen(Collectors.averagingInt(str -> Integer.valueOf(str)), String::valueOf));
System.out.println(averageValue);
// 计数
long counting = Stream.of("1", "2", "3").collect(Collectors.counting());
System.out.println(counting);
// 公因子分组
Map<Integer, List<Integer>> grouping = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collectors.groupingBy(integer -> {
if (0 == integer % 2) {
return 2;
} else if (0 == integer % 3) {
return 3;
} else {
return 1;
}
}));
System.out.println(grouping);
// 公因子分组后,再求最大值
Map<Integer, Optional<Integer>> grouping1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collectors.groupingBy(integer -> {
if (0 == integer % 2) {
return 2;
} else if (0 == integer % 3) {
return 3;
} else {
return 1;
}
},
HashMap::new,
Collectors.maxBy((integer1, integer2) -> {
if (integer1 > integer2) {
return 1;
} else if (integer1.equals(integer2)) {
return 0;
} else {
return -1;
}
})));
System.out.println(grouping1);
// 将整数连接为字符串
String joinedInts = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).map(integer -> String.valueOf(integer)).collect(Collectors.joining(",", "Numbers: ", "."));
System.out.println(joinedInts);
// 先映射,再收集
List<String> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collectors.mapping(integer -> String.valueOf(integer), Collectors.toList()));
// 分区
Map<Boolean, List<Integer>> integerPartition = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collectors.partitioningBy(integer -> 0 == integer %2));
System.out.println(integerPartition);
// 分区后规约
Map<Boolean, Integer> integerSum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collectors.partitioningBy(integer -> 0 == integer %2, Collectors.reducing(1, integer -> integer + 1, (integer, integer2) -> integer + integer2)));
System.out.println(integerSum);
// 缩减为为统计类,再执行统计操作
double ave = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collectors.summarizingInt(integer -> integer)).getAverage();
System.out.println(ave);
// 转换为双精度,再求和
double sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collectors.summingDouble(integer -> integer));
System.out.println(sum);
// 收集到新的集合类
Set<Integer> intSet = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(Collectors.toCollection(HashSet::new));
System.out.println(intSet);
// 搜集为Map,遇到重复Key值,合并value
Map<Integer, Integer> resultMap = Stream.of(1, 1, 3, 3, 6, 6, 8, 8, 10, 10).collect(Collectors.toMap(integer -> integer, integer -> integer + 1, (integer, integer2) -> integer + integer2, HashMap::new));
System.out.println(resultMap);
}
}
3.2.2 元素顺序
流中的元素都是按顺序处理的,这种顺序称为出现顺序
。如果进来的流是无序的,那么出去的流也是无序的。
3.2.3 数据并行化
并发:只有一个CPU,并且时间片分配给多个任务运行,同一时间点,只有一个任务占有时间片,这就是并发而不是并行。
并行:前提一定要有多个CPU,理想情况下一个CUP的时间片全部分配给同一个任务,在同一个时间点,这些任务每一个在运行,都占有CPU时间这就是并行。
并行分为任务并行化
和数据并行化
。数据并行化是将数据分成块,为每块数据分配单独的处理单元。任务并行化一般值执行的任务不相同,数据并行化任务相同,只是处理的数据不同。
阿姆达尔定律:如果把程序的一半并行化,无论增加多少CPU,最大处理速度只能达到原来的2倍。
3.2.3.1 并行化流操作
可以直接创建并行流(Collection.parallelSteam()),或者将当前的流并行化(Stream.parallel())。
一个简单的例子,在笔者的双核电脑上,并行流会运行在两个王权并行的线程中。
package com.qupeng.fp.stream;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class TestParallel {
public static void main(String[] args) {
sequentialMapping(); // 执行时间11s
parallelMapping(); // 本机双核,两个线程,执行时间5s
}
private static void parallelMapping() {
long starTime = System.currentTimeMillis();
IntStream.rangeClosed(1, 100).parallel().mapToObj(aInt -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("Mapping by thread %s : %d", Thread.currentThread().getId(), aInt));
return String.valueOf(aInt);
}).collect(Collectors.toList());
System.out.println("Parallelly consumed total time is: " + ((System.currentTimeMillis() - starTime) / 1000));
}
private static void sequentialMapping() {
long starTime = System.currentTimeMillis();
IntStream.rangeClosed(1, 100).mapToObj(aInt -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("Mapping by thread %s : %d", Thread.currentThread().getId(), aInt));
return String.valueOf(aInt);
}).collect(Collectors.toList());
System.out.println("Sequentially consumed total time is: " + ((System.currentTimeMillis() - starTime) / 1000));
}
}
3.2.3.1.1 限制
并行流的规约操作(reduce)要实现并行化,写代码是必须遵守以下规则:
- 组合函数初始值必须为
恒等值
。恒等值就是和其他值做reduce操作是,其他值保持不变。例如 0+x=x,0就是一个恒等值。 - 组合操作必须符合
结合律
。流中的值不变,组合操作的顺序不影响最终结果。
简单的例子,违反第一条规则,导致计算结果错误
package com.qupeng.fp.stream;
import java.util.stream.IntStream;
public class TestParallelReduce {
public static void main(String[] args) {
parallelReduce(); // 规约计算的结果是4950
parallelReduceNotIdentity(); // 非恒等的初始值,导致错误的计算结果4957
}
private static void parallelReduce() {
long starTime = System.currentTimeMillis();
int result = IntStream.rangeClosed(1, 99).parallel().reduce((aInt1, aInt2) -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("Accumulated by thread %s : %d", Thread.currentThread().getId(), aInt1 + aInt2));
return aInt1 + aInt2;
}).getAsInt();
System.out.println("Result is : " + result + ", parallelly consumed total time is: " + ((System.currentTimeMillis() - starTime) / 1000));
}
private static void parallelReduceNotIdentity() {
long starTime = System.currentTimeMillis();
int result = IntStream.rangeClosed(1, 99).parallel().reduce(1, (aInt1, aInt2) -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("Accumulated by thread %s : %d", Thread.currentThread().getId(), aInt1 + aInt2));
return aInt1 + aInt2;
});
System.out.println("Result is : " + result + ", parallelly consumed total time is: " + ((System.currentTimeMillis() - starTime) / 1000));
}
}
3.2.3.1.2 性能
影响并行化性能的因素:
- 数据大小
将数据分割后再合并会带来额外的开销,所以只有输入的数据足够大,处理分块的数据时间足够长,并行化才有意义。否则额外的开销会抵消甚至超过并行化带来的性能的优化。 - 源数据结构
源数据结构的复杂程度,将会影响数据分割的开销。大的开销将会降低并行化带来的性能提升。
JDK类库中数据结构的分解性能:
性能好的,随机读取,任意分解:
ArrayList,数据,IntStream.range
性能一般的,难以公平的分解,但是可分解:
HashSet,TreeSet
性能差的,难以分解:
LinkedList,Streams.iterate,BufferedReader.lines - 装箱
处理基本类型要比装箱类型快,所以JDK提供了专门用于3基本数据类型(int, long, double)的一系列类,如IntStream, LongStream, DoubleStream等。 - 核的数量
核的数量越多,理论上性能提升的幅度就越大。单核完全没有必要并行化。 - 单元数据处理开销
流用在每个元素身上的时间越长,性能提升越明显。
3.2.3.2 并行化数组操作
Java8还引入了针对数组的并行操作,在Arrays类里面添加了如下方法(部分重载方法并未列出):
修饰符和返回值 | 方法 | 描述 |
---|---|---|
static void | parallelPrefix(T[] array, BinaryOperator op) | 使用提供的函数并行累积给定数组的每个元素,每个元素的值变为为二元操作符的运算结果。二元操作符的输入是当前元素和前一个元素。例如,二元操作符是计算当前元素与前一个元素的和,如果数组最初保存 [2, 1, 0, 3] 并且操作执行加法,则返回时数组保存 [2, 3, 3, 6]。 对于大型数组,并行前缀计算通常比顺序循环更有效。 |
static void | parallelSetAll(T[] array, IntFunction<? extends T> generator) | 设置指定数组的所有元素,使用提供的生成器函数并行来计算每个元素。 |
static void | parallelSort(T[] a, Comparator<? super T> cmp) | 根据指定比较器产生的顺序对指定的对象数组进行排序。 |
3.2.4 日志和打印消息
流中提供了一个方法,让我们可以查看每一个值,同时还能继续流操作,这就是peek方法。我们可以peek方法中查看元素和记录日志。peek方法只有在流中的元素被消费时才会被调用。
package com.qupeng.fp.stream;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestPeek {
public static void main(String[] args) {
Stream.of("1", "2", "3").peek(System.out::println); // 不会调用peek
Stream.of("1", "2", "3").peek(System.out::println).collect(Collectors.toList()); // 收集时调用peek
Stream.of("12", "34", "56").peek(str -> System.out.println("peak: " + str + ", ")).flatMap(str -> Arrays.stream(str.split(""))).peek(str -> System.out.println("peak: " + str + ", ")).collect(Collectors.toList()); // 收集时依次调用两个peek
}
}