java 8流自定义收集器
Java 8引入了收集器的概念。 大多数时候,我们几乎不使用Collectors
类中的工厂方法,例如collect(toList())
, toSet()
或其他更有趣的方法,例如counting()
或groupingBy()
。 实际上,没有多少人真正去研究如何定义和实现收集器。 让我们从分析Collector<T, A, R>
真正含义及其工作原理开始。
Collector<T, A, R>
充当流的“接收器 ” –流将项(一个接一个)推入收集器,最后应产生一些“ 收集 ”值。 在大多数情况下,这意味着通过累积元素或将流减少为更小的内容(例如, counting()
很少的元素的toList()
收集器)来构建集合(如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>> {@Overridepublic Supplier<ImmutableSet.Builder<T>> supplier() {return ImmutableSet::builder;}@Overridepublic BiConsumer<ImmutableSet.Builder<T>, T> accumulator() {return (builder, t) -> builder.add(t);}@Overridepublic BinaryOperator<ImmutableSet.Builder<T>> combiner() {return (left, right) -> {left.addAll(right.build());return left;};}@Overridepublic Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() {return ImmutableSet.Builder::build;}@Overridepublic 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): B
。 aggregate()
就像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
java 8流自定义收集器