系列中的上一篇
当前教程
实现 Collector 接口
系列中的下一篇

系列中的上一篇: 创建您自己的 Collector

系列中的下一篇: 使用 Optional

实现 Collector 接口

 

为什么要实现 Collector 接口?

有三种方法可以创建您自己的 Collector。

在本教程中,我们研究了第一种方法。它包括使用 Collectors 工厂类提供的不同机制将现有的 Collector 组合起来,即,将 Collector 作为下游 Collector 传递给另一个 Collector,或者使用 collectingAndThen() Collector 作为完成器。

您还可以调用 collect() 方法,该方法接受构建 Collector 的三个元素。这些方法在基本类型流和对象流上都可用。它们接受我们在上一节中介绍的三个参数。

  1. 供应商用于创建可变容器,用于累积流中的元素。
  2. 累加器,由双消费者建模。
  3. 组合器,也由双消费者建模,用于组合两个部分填充的容器,在并行流的情况下使用。

第三种方法是实现 Collector 接口,并将您的实现传递给我们已经介绍过的 collect() 方法。实现您自己的 Collector 可以为您提供最大的灵活性,但它也更技术性。

 

了解 Collector 的参数类型

让我们检查一下此接口的参数。

interface Collector<T, A, R> {
    
    // content of the interface
}

让我们首先检查以下类型:TR

第一个类型是 T,它对应于此 Collector 处理的流元素的类型。

最后一个类型是 R,它是此 Collector 生成的类型。

对于 toList() Collector,在 Stream 实例上调用,类型 R 将是 List<T>。对于 toSet() Collector,它将是 Set<T>

groupingBy() 方法接受一个函数来计算返回的映射的键。如果您正在使用此 Collector 收集 Stream,那么您需要传递一个可以映射 T 实例的函数。它可以将它们映射到任何类型为 K 的实例以创建映射的键。因此,结果映射的类型将是 Map<K, List<T>>。因此,类型 R 只是这个:Map<K, List<T>>

类型 A 更难处理。您可能尝试过使用 IDE 来存储您在前面示例中创建的 Collector 之一。如果您这样做了,您可能已经意识到您的 IDE 没有为该类型提供明确的值。对于以下示例,情况也是如此。

Collection<String> strings =
        List.of("two", "three", "four", "five", "six", "seven", "eight", "nine",
                "ten", "eleven", "twelve");

Collector<String, ?, List<String>> listCollector = Collectors.toList();
List<String> list = strings.stream().collect(listCollector);

Collector<String, ?, Set<String>> setCollector = Collectors.toSet();
Set<String> set = strings.stream().collect(setCollector);

Collector<String, ?, Map<Integer, Long>> groupingBy = 
        Collectors.groupingBy(String::length, Collectors.counting());
Map<Integer, Long> map = strings.stream().collect(groupingBy);

对于所有这些 Collector,第二个参数类型只是 ?

如果您需要实现 Collector 接口,那么您将必须为 A 提供一个明确的值。类型 A 是此 Collector 使用的中间可变容器的类型。对于 toList() Collector,它将是 ArrayList,对于 toSet() Collector,它将是 HashSet。事实证明,此 A 类型被 toList() 工厂方法声明的返回类型隐藏了,这就是为什么您不能在前面的示例中将 ? 类型替换为 ArrayList<T> 的原因。

即使内部可变容器由实现直接返回,AR 类型也可能不同。例如,在 toList() Collector 的情况下,您可以通过为 A 固定 ArrayList<T> 以及为 R 固定 List<T> 来实现 Collector<T, A, R> 接口。

 

了解 Collector 的特性

Collector 定义了内部特性,流实现使用这些特性来优化此 Collector 的使用。

有三个特性。

  1. IDENTITY_FINISH 特性表示此 Collector 的完成器是恒等函数。实现不会为具有此特性的 Collector 调用完成器。
  2. UNORDERED 特性表示此 Collector 不保留处理流元素的顺序。对于 toSet() Collector 来说,情况就是这样,它具有此特性。另一方面,toList() Collector 没有此特性。
  3. CONCURRENT 特性表示累加器存储处理元素的容器支持并发访问。这一点对于并行流很重要。

这些特性在 Collector.Characteristics 枚举中定义,并由 characteristics() 方法以集合形式返回,该方法在 Collector 接口中定义。

 

实现 toList() 和 toSet() 收集器

有了这些元素,您现在可以重新创建类似于 toList() 收集器的收集器实现。

class ToList<T> implements Collector<T, List<T>, List<T>> {


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

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

    public BinaryOperator<List<T>> combiner() {
        return (list1, list2) -> {list1.addAll(list2); return list1; };
    }

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

    public Set<Characteristics> characteristics() {
        return Set.of(Characteristics.IDENTITY_FINISH);
    }
}

您可以使用以下模式使用此收集器。

Collection<String> strings =
        List.of("one", "two", "three", "four", "five") ;

List<String> result = strings.stream().collect(new ToList<>());
System.out.println("result = " + result);

此代码输出以下结果。

result = [one, two, three, four, five]

实现类似于 toSet() 收集器的收集器只需要两个修改。

 

实现 joining() 收集器

重新创建此收集器的实现很有趣,因为它只对字符串进行操作,并且它的终结器不是身份函数。

此收集器将它处理的字符串累积在一个 StringBuffer 实例中,然后调用该累积器的 toString() 方法来生成最终结果。

此收集器的特性集为空。它保留了元素处理的顺序(因此没有UNORDERED特性),它的终结器不是身份函数,并且它不能并发使用。

让我们看看如何实现此收集器。

class Joining implements Collector<String, StringBuffer, String> {

    public Supplier<StringBuffer> supplier() {
        return StringBuffer::new;
    }

    public BiConsumer<StringBuffer, String> accumulator() {
        return StringBuffer::append;
    }

    public BinaryOperator<StringBuffer> combiner() {
        return StringBuffer::append;
    }

    public Function<StringBuffer, String> finisher() {
        return Object::toString;
    }

    public Set<Characteristics> characteristics() {
        return Set.of();
    }
}

您可以在以下示例中看到如何使用此收集器。

Collection<String> strings =
        List.of("one", "two", "three", "four", "five") ;

String result = strings.stream().collect(new Joining());
System.out.println("result = " + result);

运行此代码会产生以下结果。

result = onetwothreefourfive

支持分隔符、前缀和后缀将使用 StringJoiner 而不是 StringBuilder,它已经支持这些元素。


上次更新: 2021 年 9 月 14 日


系列中的上一篇
当前教程
实现 Collector 接口
系列中的下一篇

系列中的上一篇: 创建您自己的 Collector

系列中的下一篇: 使用 Optional