Java 8编写自定义收集器简介

Java 8引入了收集器的概念。 大多数时候,我们几乎不使用Collectors类中的工厂方法,例如collect(toList())toSet()或其他更花哨的方法,例如counting()groupingBy() 。 实际上,没有多少人真正去研究如何定义和实现收集器。 让我们从分析Collector<T, A, R>真正含义及其工作原理开始。

Collector<T, A, R>充当流的“接收 ” –流将项(一个接一个)推入收集器,最后应产生一些“ 收集 ”值。 在大多数情况下,这意味着通过累积元素或将流减少到较小的对象(例如, counting()收集器仅计算元素)来构建集合(如toList() )。 每个收集器都接受类型T项,并产生类型R聚合(累积)值(例如R = List<T> )。 泛型A简单定义了中间可变数据结构的类型,在此期间,我们将使用它来累积T型项。 类型A可以但不必与R相同-简单来说,我们用来从输入Stream<T>收集项目的可变数据结构可以不同于实际的输出收集/值。 话虽如此,每个收集器都必须实现以下方法:

interface Collector<T,A,R> {
    Supplier<A>          supplier()
    BiConsumer<A,T>      acumulator() 
    BinaryOperator<A>    combiner() 
    Function<A,R>        finisher()
    Set<Characteristics> characteristics()
}
  • supplier()返回一个函数,该函数创建一个累加器实例–可变数据结构,我们将使用该函数来累加类型T输入元素。
  • accumulator()返回一个函数,该函数将累加累加器和类型T一项,即累加累加器。
  • combiner()用于将两个累加器合并为一个。 它在并行执行收集器时使用,首先拆分输入Stream<T>并首先独立收集部分。
  • finisher()使用累加器A并将其转换为类型R的结果值,例如collection。 所有这些听起来都非常抽象,所以让我们做一个简单的例子。

显然,Java 8没有为Guava提供ImmutableSet<T>的内置收集器。 但是,创建一个非常简单。 请记住,为了迭代地构建ImmutableSet我们使用ImmutableSet.Builder<T> –这将是我们的累加器。

import com.google.common.collect.ImmutableSet;

public class ImmutableSetCollector<T> 
        implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {
    @Override
    public Supplier<ImmutableSet.Builder<T>> supplier() {
        return ImmutableSet::builder;
    }

    @Override
    public BiConsumer<ImmutableSet.Builder<T>, T> accumulator() {
        return (builder, t) -> builder.add(t);
    }

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

    @Override
    public Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() {
        return ImmutableSet.Builder::build;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return EnumSet.of(Characteristics.UNORDERED);
    }
}

首先,仔细研究泛型类型。 我们的ImmutableSetCollector接受类型T输入元素,因此它适用于任何Stream<T> 。 最后,将产生预期的ImmutableSet<T>ImmutableSet.Builder<T>将成为我们的中间数据结构。

  • supplier()返回创建新ImmutableSet.Builder<T>的函数。 如果您不熟悉Java 8中的lambda,则ImmutableSet::builder() -> ImmutableSet.builder()的简写。
  • accumulator()返回一个函数,该函数采用builder和一个T类型的元素。 它只是将上述元素添加到构建器中。
  • combiner()返回一个函数,该函数将接受两个生成器,并通过将一个中的所有元素添加到另一个中并返回后者来将它们变成一个。 最后finisher()返回一个函数,该函数会将ImmutableSet.Builder<T>转换为ImmutableSet<T> 。 同样,这是以下形式的简写语法: builder -> builder.build()
  • 最后但并非最不重要的一点是, characteristics()告知JDK我们的收集器具有什么功能。 例如,如果ImmutableSet.Builder<T>是线程安全的(不是),我们也可以说Characteristics.CONCURRENT

现在,我们可以使用collect()在所有地方使用自定义收集器:

final ImmutableSet<Integer> set = Arrays
        .asList(1, 2, 3, 4)
        .stream()
        .collect(new ImmutableSetCollector<>());

但是创建新实例有点冗长,因此我建议创建静态工厂方法,类似于JDK所做的:

public class ImmutableSetCollector<T> implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {

    //...

    public static <T> Collector<T, ?, ImmutableSet<T>> toImmutableSet() {
        return new ImmutableSetCollector<>();
    }
}

从现在开始,我们只需键入以下命令即可充分利用我们的自定义收集器: collect(toImmutableSet()) 。 在第二部分中,我们将学习如何编写更复杂和有用的收集器。

更新资料

@akarazniewicz 指出收藏家只是折叠的冗长实现。 由于我与褶皱之间的关系,我不得不对此发表评论。 Java 8中的收集器基本上是Scala中最复杂的折叠类型的面向对象封装,即GenTraversableOnce.aggregate[B](z: ⇒ B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): Baggregate()类似于fold() ,但是需要额外的combop才能将两个B型累加器组合为一个。 将其与收集器进行比较,参数z来自seqop() supplier()seqop()归约运算是一个accumulator()combop是一个combop combiner() 。 用伪代码可以编写:

finisher(
    seq.aggregate(collector.supplier())
        (collector.accumulator(), collector.combiner()))

GenTraversableOnce.aggregate()在可能同时减少时使用GenTraversableOnce.aggregate()就像收集器一样)。

翻译自: https://www.javacodegeeks.com/2014/07/introduction-to-writing-custom-collectors-in-java-8.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值