Collector接口包含了一系列方法,为实现具体的归约操作(即收集器)提供了范本。我们已经看过了Collector接口中实现的许多收集器(由Collector接口的工具类Collectors提供),例如toList()或groupingBy()。

这也意味着你可以为Collector接口提供自己的实现,从而自由地创建自定义归约操作。

要实现自定义收集器,只需要实现java.util.stream.Collector<T, A, R>接口即可.

Collector接口的声明如下:

public interface Collector<T, A, R> {

    Supplier<A> supplier();

    BiConsumer<A, T> accumulator();

    BinaryOperator<A> combiner();

    Function<A, R> finisher();

    Set<Characteristics> characteristics();
}

泛型介绍

  • T:stream在调用collect方法收集前的数据类型
  • A:A是T的累加器,遍历T的时候,会把T按照一定的方式添加到A中,换句话说就是把一些T通过一种方式变成A
  • R:R可以看成是A的累加器,是最终的结果,是把A汇聚之后的数据类型,换句话说就是把一些A通过一种方式变成R

接口介绍

  • supplier: 怎么创建一个累加器
  • accumulator:怎么把一个对象添加到累加器中
  • combiner: 怎么把一个累加器和另一个累加器合并起来,此方法并行时才会调用
  • finisher: 怎么把A转化为R
  • characteristics: 特征值,告诉collect方法在执行归约操作的时候可以应用哪些优化

Characteristics

包含三个项目的枚举:

  • UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响
  • CONCURRENT:accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED, 那它仅在用于无序数据源时才可以并行归约。
  • IDENTITY_FINISH:这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用做归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的。

当Collector设置为IDENTITY_FINISH,finisher方法不会调用,因为不用再类型转换了,中间数据类型就是最终的数据类型。

Stream#collect()源码分析

下面的Stream的实现类ReferencePipeline的collect方法的源码

public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) { // @1
	A container;
	if (isParallel()
		&& (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))
		&& (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) { // @2
		container = collector.supplier().get(); // @3
		BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
		forEach(u -> accumulator.accept(container, u));
	}
	else {
		container = evaluate(ReduceOps.makeRef(collector)); // @4
	}
	return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
		? (R) container
		: collector.finisher().apply(container); // @5
}
  • 代码@1:函数声明,该方法返回的结果类型为R,传入的行为参数接口为Collector。
  • 代码@2:判断是否符合并行化累积与规约的条件。
  • 是否是并行流,Stream.stream()方法的流是非并行化流,如果要支持并行化执行,需要使用Stream.parallelStream()方法。
  • Collector(收集器,行为化参数)中收集器行为集合中是否包含Characteristics.CONCURRENT(并行执行),如果不包含该行为,则不支持并行执行。
  • 原始流是否有顺序或者收集器的行为集合中明确包含Characteristics.UNORDERED(不要求顺序性)。
  • 上述三个条件必须同时满足,才能并行执行,否则串行执行。
  • 代码@3:并行执行收集动作。
  • 代码@4:串行执行收集动作。
  • 代码@5:如果收集器收集行为集合中包含Characteristics.IDENTITY_FINISH,则直接返回原始值,否则使用Collector.finishier()方式对计算的值进行函数式计算。

自定义toList

package com.morris.java8.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;

public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {

    @Override
    public Supplier<List<T>> supplier() {
        return ArrayList::new;
    }

    @Override
    public BiConsumer<List<T>, T> accumulator() {
        return List::add;
    }

    @Override
    public BinaryOperator<List<T>> combiner() {
        return (left, right) -> {
            left.addAll(right);
            return left;
        };

    }

    @Override
    public Function<List<T>, List<T>> finisher() {
        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
    }

    public static void main(String[] args) {
        List<Dish> dishList = Dish.createList().stream().filter(Dish::isVegetarian).collect(new ToListCollector<>());
        System.out.println(dishList);
    }
}

自定义joining

package com.morris.java8.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;

public class JoiningCollector implements Collector<String, StringBuilder, String> {


    private String seperator = ",";

    public JoiningCollector() {
    }

    public JoiningCollector(String seperator) {
        this.seperator = seperator;
    }

    @Override
    public Supplier<StringBuilder> supplier() {
        return StringBuilder::new;
    }

    @Override
    public BiConsumer<StringBuilder, String> accumulator() {
        return (sb, str) -> sb.append(str).append(seperator);
    }

    @Override
    public BinaryOperator<StringBuilder> combiner() {
        return StringBuilder::append;
    }

    @Override
    public Function<StringBuilder, String> finisher() {
        return c -> {
            String ret = c.toString();
            if (ret.endsWith(seperator)) {
                return ret.substring(0, ret.length() - 1);
            }
            return ret;
        };
    }

    @Override
    public Set<Characteristics> characteristics() {
        return new HashSet<>();
    }

    public static void main(String[] args) {
        String collect = Arrays.asList("hello", "world", "java", "stream").stream().collect(new JoiningCollector("|"));
        System.out.println(collect);
    }

}