MST

星途 面试题库

面试题:Java Stream collect方法在并行流与自定义Collector中的深度应用

在并行流场景下,使用Stream的collect方法结合自定义Collector实现一个功能:将一组字符串按长度分组,并且每个分组内的字符串按字典序排序,最后统计每个分组的字符串数量。要求实现自定义Collector,并确保在并行流中能够正确工作。请详细说明实现步骤及自定义Collector中各个方法的作用。
14.5万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试
  1. 实现步骤
    • 定义数据结构:我们需要一个Map<Integer, List<String>>来按长度分组字符串,同时还需要统计每个分组的字符串数量,所以可以使用Map<Integer, Map.Entry<List<String>, Long>>,其中List<String>是按字典序排序后的字符串列表,Long是该分组的字符串数量。
    • 实现自定义Collector
      • supplier方法:创建一个空的HashMap用于存储分组结果。
      • accumulator方法:将每个字符串按长度分组,并添加到对应的列表中。
      • combiner方法:合并两个Map,将相同长度分组的列表合并,并更新数量。
      • finisher方法:对每个分组内的字符串列表按字典序排序,并统计数量。
      • characteristics方法:返回Collector.Characteristics.IDENTITY_FINISHCollector.Characteristics.CONCURRENT,表示finisher方法返回的结果与累加器类型相同,并且该收集器可以在并行流中正确工作。
  2. 自定义Collector代码实现
import java.util.*;
import java.util.stream.Collector;

public class StringLengthGroupingCollector implements Collector<String, Map<Integer, List<String>>, Map<Integer, Map.Entry<List<String>, Long>>> {
    @Override
    public Supplier<Map<Integer, List<String>>> supplier() {
        return () -> new HashMap<>();
    }

    @Override
    public BiConsumer<Map<Integer, List<String>>, String> accumulator() {
        return (map, str) -> {
            int length = str.length();
            map.computeIfAbsent(length, k -> new ArrayList<>()).add(str);
        };
    }

    @Override
    public BinaryOperator<Map<Integer, List<String>>> combiner() {
        return (map1, map2) -> {
            map2.forEach((length, list) -> map1.computeIfAbsent(length, k -> new ArrayList<>()).addAll(list));
            return map1;
        };
    }

    @Override
    public Function<Map<Integer, List<String>>, Map<Integer, Map.Entry<List<String>, Long>>> finisher() {
        return map -> {
            Map<Integer, Map.Entry<List<String>, Long>> result = new HashMap<>();
            map.forEach((length, list) -> {
                Collections.sort(list);
                result.put(length, new AbstractMap.SimpleImmutableEntry<>(list, (long) list.size()));
            });
            return result;
        };
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Set.of(Collector.Characteristics.IDENTITY_FINISH, Collector.Characteristics.CONCURRENT);
    }
}
  1. 使用示例
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("apple", "banana", "cherry", "date", "fig");
        Map<Integer, Map.Entry<List<String>, Long>> result = strings.parallelStream()
              .collect(new StringLengthGroupingCollector());
        result.forEach((length, entry) -> {
            System.out.println("Length: " + length);
            System.out.println("Sorted Strings: " + entry.getKey());
            System.out.println("Count: " + entry.getValue());
            System.out.println();
        });
    }
}
  1. 自定义Collector中各个方法的作用
    • supplier
      • 作用:创建一个可变的结果容器,用于累加数据。在我们的例子中,它创建一个空的HashMap,用于后续按字符串长度分组存储字符串。
    • accumulator
      • 作用:将输入元素添加到结果容器中。这里将每个字符串按其长度分组,添加到HashMap中对应长度的列表里。
    • combiner
      • 作用:合并两个部分结果。在并行流中,会有多个部分结果需要合并,此方法将两个Map中相同长度分组的字符串列表合并在一起。
    • finisher
      • 作用:对最终的结果容器进行转换,生成最终想要的结果类型。这里对每个分组内的字符串列表进行字典序排序,并统计数量,生成最终的Map<Integer, Map.Entry<List<String>, Long>>结果。
    • characteristics
      • 作用:返回该收集器的特性。IDENTITY_FINISH表示finisher方法返回的结果与累加器类型相同,CONCURRENT表示该收集器可以在并行流中正确工作。