Java 8 自定义流Collector实现

Java 8 自定义流Collector实现

前文我们看到 Java 8 Collectors提供了很多内置实现。但有时我们需要实现一些特定功能满足业务需要,本文带你学习如何自定义Collector的实现,计算字符串流中所有单词的长度。

需求说明

假设有字符串流,利用每个字符串对象有方法length()————计算并返回单词长度。我们想创建自定义Collector,实现reduce操作,计算流中所有单词的长度之和。

使用 Collector.of() 方法

为了创建自定义Collector,需要实现Collector接口。现在,我们不使用传统方法,而是使用Collector.of()静态方法创建自定义Collector。

不仅是为了更精简和增强可读性,还因为这种方法可以忽略部分不必要的实现。实际上,Collector接口仅需要三个必须部分————提供者(supplier), 累加器(accumulator) 以及合并器(combiner)。

结果容器提供者(supplier)

实现Collector,必须提供结果容器,即累加值存储的地方。下面代码提供了结果容器:

() -> new int[1]

你可能会想,为什么是 new int[1],而不是int变量作为初始化值。原因是Collector接口需要结果容器能够以可变的方式进行更新。

累加元素(accumulator)

接下来,我们需要创建函数实现增加元素至结果容器。在我们的示例中,即单词的长度增加至结果容器:

(result, item) -> result[0] += item.length()

该函数是Consumer类型,其不返回任何值,仅以可变的方式更新结果容器————即数组中的第一个元素。

合并器(combiner)

在reduction序列操作中,提供者(supplier) 和 累加器(accumulator) 已经足够了,但为了能够实现并行操作,我们需要实现一个合并器。合并器(combiner)是定义两个结果如何合并的函数。
在并行环境下,流被分为多个部分,每个部分被并行累加。当所有部分都完成时,结果需要使用合并器函数进行合并。下面请看我们的实现代码:

(result1, result2) -> {
  result1[0] += result2[0];
  return result1;
}

最小的自定义Collector

现在我们已经所有必要组件,整合在一起就是我们的Collector:

 wordStream.collect(Collector.of(
    ()-> new int[1],
    (result, item) -> result[0] += item.length(),
    (result1, result2) -> {
        result1[0] += result2[0];
        return result1;
    }
));

上面方案有个小问题,其直接返回结果容器即int[]类型。实际我们需要的字符串长度,不是结果容器。

最后一个转换

我们可以很容易实现,增加一个函数,其映射结果容器至我们需要的类型。这里我们仅仅需要数组的第一个元素:

total -> total[0]  

最后完整代码为:

    private List<String> wordList = Arrays.asList("tommy", "is", "a", "java", "developer");

    @Test
    public void wordCountTest() {
        Stream<String> wordStream = wordList.stream();

        int wordCnt = wordStream.collect(Collector.of(
            ()-> new int[1],
            (result, item) -> result[0] += item.length(),
            (result1, result2) -> {
                result1[0] += result2[0];
                return result1;
            },
            total -> total[0]
        ));

        System.out.println("wordCnt = " + wordCnt);
    }

如果把我们自定义的Collector赋值给变量,则代码可以简化为:

int wordCount = wordStream.collect(totalWordCountCollector);  

优化参数

最后,我们看下优化参数。即自定义Collector支持不同类型的优化参数。
使用Collector.of() 可以在参数最后增加 Characteristics 作为可变参数:

Collector.of(  
  // supplier,
  // accumulator,
  // combiner,
  // finisher, 
  Collector.Characteristics.CONCURRENT,
  Collector.Characteristics.IDENTITY_FINISH,
  // ...
);

有三种 Characteristics 可以使用:

  • CONCURRENT — 指明结果容器可以被多个并发累加器使用
  • IDENTITY_FINISH — 指明结束函数是恒等函数且可以被忽略
  • UNORDERED — 指明collector不依赖元素顺序

总结

本文我们学习了如何自定义Java 8 自定义 流的 Collector实现.更多内容可以参考官方文档

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: Apache Flink支持通过实现ReduceFunction和GroupReduceFunction接口来实现自定义collect_set函数。下面是一个示例Java代码:public class CollectSetReducer implements ReduceFunction<String> { public String reduce(String value1, String value2) throws Exception { Set<String> set = new HashSet<String>(); set.add(value1); set.add(value2); return set.stream().collect(Collectors.joining(",")); } } ### 回答2: Apache Flink是一个处理和批处理框架,它提供了丰富的内置操作符和函数来处理式和批处理数据。然而,Apache Flink没有提供内置的collect_set函数,用于将数据中的元素收集到一个集合中。 要在Apache Flink中自定义实现collect_set函数,您可以使用Flink提供的ReduceFunction和RichFlatMapFunction接口来实现。下面是一个示例的Java代码实现: 首先,我们需要自定义一个ReduceFunction实现,用于将相同key的元素合并到一个集合中: ```java public class CollectSetReduceFunction<T> implements ReduceFunction<T> { @Override public T reduce(T value1, T value2) throws Exception { // 将value2合并到value1中 // 这里假设value1和value2是集合类型 if (value1 instanceof Set) { ((Set) value1).addAll((Set) value2); return value1; } return null; } } ``` 接下来,我们需要自定义一个RichFlatMapFunction实现,用于将每个元素发送到下游操作符,并将其添加到collect_set的集合中: ```java public class CollectSetFunction<T> extends RichFlatMapFunction<T, Set<T>> { private Set<T> resultSet; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); resultSet = new HashSet<>(); } @Override public void flatMap(T value, Collector<Set<T>> out) throws Exception { resultSet.add(value); } @Override public void close() throws Exception { super.close(); out.collect(resultSet); } } ``` 最后,您可以在Flink的数据中使用自定义collect_set函数,例如: ```java DataStream<Tuple2<String, Integer>> dataStream = ... // 输入数据 DataStream<Set<Integer>> resultStream = dataStream .groupBy(0) // 按key分组 .reduce(new CollectSetReduceFunction<>()) // 自定义reduce函数 .flatMap(new CollectSetFunction<>()); // 自定义flatMap函数 resultStream.print(); // 输出结果 ``` 以上是一个简单的示例,用于演示如何在Apache Flink中自定义实现collect_set函数。根据您的具体需求,您可能需要根据数据类型和业务逻辑进行一些修改和调整。 ### 回答3: Apache Flink是一个开源的处理框架,它提供了各种数据操作和处理功能。如果想要实现类似于collect_set的功能,可以使用Flink的自定义函数来完成。 在Java中,我们可以创建一个自定义的聚合函数,来实现collect_set的功能。聚合函数可以让我们对输入的数据进行逐条处理,并输出最终的聚合结果。 以下是一个使用Java代码实现collect_set功能的示例: ```java import org.apache.flink.api.common.functions.AggregateFunction; import java.util.HashSet; import java.util.Set; public class CollectSetFunction<T> implements AggregateFunction<T, Set<T>, Set<T>> { @Override public Set<T> createAccumulator() { return new HashSet<>(); } @Override public Set<T> add(T value, Set<T> accumulator) { accumulator.add(value); return accumulator; } @Override public Set<T> getResult(Set<T> accumulator) { return accumulator; } @Override public Set<T> merge(Set<T> a, Set<T> b) { a.addAll(b); return a; } } ``` 在这个示例中,我们实现了`AggregateFunction`接口,并重写了其中的四个方法来完成collect_set的功能。 `createAccumulator()`方法用于创建一个空的累加器,这里我们使用HashSet来存储结果集。 `add()`方法会在每个输入数据上被调用,它将每个输入元素添加到累加器中。 `getResult()`方法在处理完所有元素后返回最终的结果。 `merge()`方法用于合并多个并行计算的累加器。 通过将这个自定义聚合函数应用到Flink的数据中,我们就可以实现类似于collect_set的功能,将相同的元素放入一个集合中。 ```java DataStream<Tuple2<String, Integer>> input = ...; // 输入数据 DataStream<Tuple2<String, Set<Integer>>> result = input .keyBy(0) .aggregate(new CollectSetFunction<>()); ``` 在这个示例中,我们先将输入数据按照某个键值进行分组(这里使用第一个字段作为键),然后应用我们自定义的聚合函数`CollectSetFunction`。 最终的结果数据`result`将包含分组后的数据及其对应的集合。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值