实现Collector接口

为什么要实现Collector接口?

有三种方法可以创建您自己的收集器。
我们在本教程中研究了第一个。它包括将现有收集器与collector工厂类提供的不同机制相结合,即将一个收集器作为下游收集器传递给另一个收集器,或者使用collectingAndThen()收集器的完成器。

您还可以调用collect()方法,该方法接受构建收集器所需要的三个元素。这些方法在基本类型流和对象流中都可用。它们采用了我们在前几节中介绍的三个参数。

  • supplier :用于创建在其中积累流元素的可变容器
  • accumulator:累加器,由biconsumer建模
  • combiner:组合器,也由双向使用者建模,用于组合两个部分填充的容器,用于并行流。

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

理解Collector的参数类型

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

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

让我们首先检查以下类型:T和R。
第一种类型是T,它对应于收集器正在处理的流的元素类型。
最后一种类型是R,它是这个收集器产生的类型。
对于在Stream实例上调用的toList()收集器,类型R将是List。对于toSet()收集器,它将是Set。

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

类型A比较复杂。您可能已经尝试使用IDE来存储在前面示例中创建的一个收集器。如果您这样做了,您可能会意识到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接口,那么您必须给A一个显式的值。类型A是该收集器使用的中间可变容器的类型。对于toList()收集器,它将是ArrayList,对于toSet()收集器,它将是HashSet。结果是,这个A类型被toList()工厂方法声明的返回类型隐藏了,这就是为什么前面的例子中?不能替换为ArrayList类型。

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

了解Collector的特性

收集器定义的被流实现使用的内部特征用于优化该收集器的使用。
有三个特点。

  • IDENTITY_FINISH特征表明此收集器的结束器是标识函数。该实现不会为具有此特性的收集器调用完成器。
  • 无序特征表明该收集器不保留处理流元素的顺序。这就是toSet()收集器的情况,它具有这个特性。另一方面,toList()收集器没有它。
  • CONCURRENT特征表明累加器存储已处理元素的容器支持并发访问。这一点对于平行流很重要。

这些特征在Collector.Characteristics枚举中定义,并由Collector接口上定义的Characteristics()方法在集合中返回。

实现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()收集器的收集器只需要两处修改。

  • supplier()方法将返回HashSet::new。
  • characteristics()方法将添加characteristics.UNORDERED返回集合。

实现join()Collector

重新创建这个收集器的实现很有趣,因为它只对字符串进行操作,而它的完成器不是标识函数。

这个收集器在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,前者已经支持这些元素。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值