Stream可以说是java8的一大亮点。java8中的Stream对集合功能进行了增强。在日常开发中,我们免不了要经常对集合对象进行处理,而在java8以前们,对于集合的处理完全是由我们自己来操作,所以代码看起来相对繁杂。而又Stream以后,对于集合的处理得到了大大的简化。Stream提供了对集合对象的各种非常便利的,高效的聚合操作。
集合和Stream,表面看起来很相似,却有着不同的目标。集合关注的是它当中元素有效的管理和访问。与集合不同,流不会对它当中的元素提供一种直接访问的方式,它关注的是计算。Stream关注的是它的源source的各种聚合的计算操作。这也是集合和流的本质区别。
一般来说Stream可分为三个部分:源操作source,中间操作Intemediate和终止操作Terminal。
流的源可以是一个数组,一个集合,一个生成器方法,一个I/O通道等等。
一个流可以又零个或者多个中间操作,每一个中间操作都会返回一个新的流,供下一个操作使用但是一个流只会又一个终止操作。中间操作都是惰性的,也就是说仅仅调用流的中间操作,其实并没有真正开始流的源的遍历。
一个流只能有一个终止操作,它必定是流的最后一个操作。只有调用了流的终止操作,流的源的元素才会真正的开始遍历,并且会生成一个结果返回或者产生一个副作用(side-effect).
另外,每一个流只能被使用一次(即调研红中间操作或者终止操作)。如果检测到流被重用,会抛出IllegalStateException异常,所以才使用流的时候,建议采用链式的写法,如下:
List<Integer> list = Arrays.asLis(1,2,3,4,5,6);
int sum = list.stream().map(item -> tiem*2).reduce(0, Integer::sum);
从表面上来看,好像流在执行了多个中间操作和一个终止操作之后,对每一个操作,流中的元素都会遍历执行,也就是有几个操作,流中的元素就会进行几次遍历。这种观点是打错特错的。
流的实际执行流程是这样的,在遇到中间操作的时候,其实只是构建了一个Pipeline对象,而该对象是一个双向链表的数据结构,只有在遇到终止操作的时候,哪些中间操作和终止操纵会被封装成链表的数据结构链接起来,而流中每一个元素只会按照顺序链接的去执行这些操作,也就是说,流中的元素最终只会在遇到终止操作后遍历一次,
map操作是将流中的元素映射成另外一种元素,接收一个Function类型的参数,是一个中间操作。
//转换大写
List<String> wordList= Arrays.asList("a","b","c","d","E","F");
List<String> collect = wordList.stream().map(String::toUpperCase).collect(Collectors.toList());
System.out.println("collect == "+collect);
//求平方
List<Integer> numlist = Arrays.asList(1, 2, 3, 4, 5, 6, 4, 2, 6, 5, 7);
List<Integer> collect1 = numlist.stream().map(item -> item * item).distinct().collect(Collectors.toList());
System.out.println("collect1 == "+ collect1);
flatMap是一种打平的映射。可用于一堆多的操纵。也是一个中间操作例子:
//flatMap用于一对多操作
Stream<List<Integer>> listStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6));
Stream<Integer> integerStream = listStream.flatMap(childList -> childList.stream());
看到最后返回的是Stream类型,原来Stream中集合元素被打平了。
filter是对流中的元素进行过滤操作,会接收一个Predicate类型的参数,是一个中间操作。只要是不满足这个predicate的,也就是说predicate.test()返回false的元素会被过滤掉。
//找出偶数
Integer[] sixNums = {1,2,3,4,5,6,7,8,9,10,11};
Integer[] integers = Stream.of(sixNums).filter(var -> var % 2 == 0).toArray(Integer[]::new);
System.out.println("integers == "+ integers.toString());
distinct操作是对流中的元素进行去重,是一个中间操作。
//distinct去重
List<Integer> list = Arrays.asList(6, 5, 5, 4, 2, 3, 4, 6, 7, 10, 15, 23, 15);
List<Integer> collect2 = list.stream().map(item -> item * 2).distinct().collect(Collectors.toList());
System.out.println("collect2 == "+collect2);
sorted操作是对流中的元素按照进行排序,是一个中间按操作。不带参数的是按照自然顺序进行排序。带参数的会传一个Comparator类型的参数,作为比较规则。
//sorted排序
List<Integer> list1 = Arrays.asList(6, 5, 5, 4, 2, 3, 4, 6, 7, 10, 15, 23, 15);
List<Integer> collect3 = list1.stream().map(n -> n * n).distinct().sorted().collect(Collectors.toList());
System.out.println("collect3 == "+collect3);
limit获取流中前n个元素返回,是一个中间操作。另外这个是一个短路操作(short-circuiting).也就是说流中的元素遍历到了第n个过后,后面的元素就不再进行遍历了。
//limit
List<Person> persons = new ArrayList();
for (int i = 1; i <= 10000; i++) {
Person person = new Person(i, "name" + i);
persons.add(person);
}
List<String> personList2 = persons.stream()
.map(Person::getName)
.limit(5)
.collect(Collectors.toList());
skip操作时跳过流总的前n个元素,是一个中间操作。
forEach操作是流中的元素遍历并且执行一个action。这是一个终止操作。
将流转换为一个数组。是一个终止操作。
reduce汇聚操作,是一个终止操作。这个方法的主要作用是把Stream元素组合起来,它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面Stream的第一个,第二个,第n个元素组合。从这个意义上说,字符串拼接,数值的sum,min,max,average都是特殊的reduce。例如Stream的sum就相当于:
Integer sum = integers.reduce(0, (a, b) -> a+b);
Integer sum = integers.reduce(0, Integer::sum);
求最大值最小值,是一个终止操作。
计算流中元素的个数。是一个终止操作。
这些操作都是终止操作,且都是短路操作。
allMatch: Stream中全部元素符合传入的predicate,返回true,只要有一个不满足就返回false;
anyMatch: Stream中只要有一个元素符合传入的predicate,返回true。只要有一个满足就返回true;
noneMatch:Stream中没有一个元素符合传入的predicate,返回true。只要有一个满足就返回false;
findFirst:找到第一个元素。找到了就直接返回,不再遍历后面的元素。
findAny:找到任何一个元素就会返回。
对流中元素执行一个可变汇聚操作。是一个终止操作。比如:将流中的元素放入到一个List集合当中,将流中的元素进行分组,分区,求和等等操作。接受一个收集器Collector对象。
// 字符串拼接
System.out.println(Stream.of("a", "b", "c", "d").collect(Collectors.joining()));
// 根据task的类型进行分组
private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) {
return tasks.stream().collect(groupingBy(Task::getType));
}
Collector是一接口。通过上面的介绍我们知道Stream的collect方法会接受一个Collector类型的参数,用来进行汇聚操作。那么是怎样实现汇聚操作的呢?
通过阅读jdk的文档可以知道,Collector接口是用来定义一个可变的汇聚操作:将输入元素累加到一个可变结果容器中,当所有的输入元素都被处理过后,选择性的将累加结果转换为一个最终的表示。汇聚操作可以被串行和并行的执行。
以下是Collector接口的定义:
public interface Collector<T, A, R> {
/**
* 用来创建并且返回一个可变结果容器
*/
Supplier<A> supplier();
/**
* 将一个值叠进一个可变结果容器
*/
BiConsumer<A, T> accumulator();
/**
* 接受两个部分结果并将它们合并。可能是把一个参数叠进另一个参数并且返回另一个参数,
* 也有可能返回一个新的结果容器,多线程处理时会用到
*/
BinaryOperator<A> combiner();
/**
* 将中间类型执行最终的转换,转换成最终结果类型
* 如果属性 IDENTITY_TRANSFORM 被设置,该方法会假定中间结果类型可以强制转成最终结果
* 类型
*/
Function<A, R> finisher();
/**
* 收集器的属性集合
*/
Set<Characteristics> characteristics();
public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Characteristics... characteristics) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(combiner);
Objects.requireNonNull(characteristics);
Set<Characteristics> cs = (characteristics.length == 0)
? Collectors.CH_ID
: Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH,
characteristics));
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs);
}
public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Characteristics... characteristics) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(combiner);
Objects.requireNonNull(finisher);
Objects.requireNonNull(characteristics);
Set<Characteristics> cs = Collectors.CH_NOID;
if (characteristics.length > 0) {
cs = EnumSet.noneOf(Characteristics.class);
Collections.addAll(cs, characteristics);
cs = Collections.unmodifiableSet(cs);
}
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
}
enum Characteristics {
CONCURRENT,
UNORDERED,
IDENTITY_FINISH
}
可以看到接口中定义了5个抽象方法,各个方法的作用都给出了注释,其实Stream的collect操作就是调用这接口中定义的方法来实现汇聚操作的,不同的汇聚操作这些方法需要有不同的实现。
Collectors类中只有一个私有的无参构造方法,而且里面提供了大量的静态方法,这些方法最终都是返回一个Collector收集器,因此可以认为Collectors这个类是Collector收集器的一个工厂类。Collectors里面定义了一个静态内部类CollectorImpl,该类是Collector收集器的一个实现:
static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A,R> finisher,
Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
}
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
@Override
public BiConsumer<A, T> accumulator() {
return accumulator;
}
@Override
public Supplier<A> supplier() {
return supplier;
}
@Override
public BinaryOperator<A> combiner() {
return combiner;
}
@Override
public Function<A, R> finisher() {
return finisher;
}
@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
}
可以看到,它针对Collector中的5个方法的返回类型定义了五个对应的类型成员变量,而抽象方法的实现是直接返回这5个成员变量。而这五个对象是在构造CollectorImpl的时候传进来的,这些都是函数式接口类型。看源码还可以看到Collectors中的静态方法其实很多就是new一个CollectorImpl对象返回,而Collector中抽象方法的实现直接一lambda的形式直接通过CollectorImpl构造方法的参数传递过去。
这里有必要对Collector这四个抽象方法结合文档注释还是很好理解的。
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
下面以Collector的toList方法来做一个讲解,以下是toList方法的实现:
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
可以看到,
supplier方法的实现为:ArrayList:new, 创建一个ArrayList对象并返回。
accumulator方法的实现为:List::add,将流中的元素添加进上面创建的ArrayList对象。
combiner方法的实现为:((left, right) -> { left.addAll(right); return left; }, 对于两个中间结果容器ArrayList,将一个的所有元素添加进另外一个,并返回另外一个ArrayList。
上面的代码跟进去,发现finisher方法的实现只是将ArrayList用List类型返回。
而characteristics方法的实现就是返回静态常量CH_ID,它是一个包含了IDENTITY_FINISH的集合,标示中间结果是可以直接向最终结果进行强制类型转换的。
以上就是toList的汇聚操作。
串行实现: Collector的汇聚操作的串行实现(即单线程)将会使用supplier方法创建唯一的一个结果容器,并且每一个输入元素会调用一次accumulator方法。
并行实现:Collector的汇聚操作的并行实现(即多线程)将会对输入元素进行分区,并且对每一个分区会使用supplier方法创建一个结果容器,然后将各个分区的每一个元素都调用accumulator方法将分区的内容计算出一个子结果,最后通过combiner方法将这些子结果合并成一个最终结果。
以上是jdk文档对Collector串行和并行实现的一个介绍。
为了确定串行与并行结果的等价性,Collector函数需要满足两个约束条件:identity(同一性)associativity(结合性)。
A a1 = supplier.get();
accumulator.accept(a1, t1);
accumulator.accept(a1, t2);
R r1 = finisher.apply(a1); // result without splitting
A a2 = supplier.get();
accumulator.accept(a2, t1);
A a3 = supplier.get();
accumulator.accept(a3, t2);
R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting
JDK还规定,基于Collector实现汇聚操作的库,例如Stream.collect(Collector),必须满足以下约束:
转自: https://blog.csdn.net/zw19910924/article/details/76945279