MST

星途 面试题库

面试题:Java Stream API深度优化及自定义收集器

请深入探讨Java Stream API的底层实现机制,以及如何在复杂业务场景下对其进行深度优化。比如,在涉及到多阶段处理且中间结果需要复用的情况下,怎样设计Stream操作以避免重复计算。此外,请阐述如何自定义一个收集器,使其在Stream操作中能高效地完成特定的数据聚合任务,例如,自定义一个收集器用于统计集合中元素出现的频率并按频率降序排列。
43.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

Java Stream API底层实现机制

  1. 数据源:Stream 可以从多种数据源获取数据,如集合、数组、I/O 通道等。数据源为 Stream 提供数据序列。
  2. 中间操作
    • 中间操作返回一个新的 Stream,且是惰性求值的。例如 filtermap 等操作,它们不会立即执行,而是将操作记录下来。
    • 底层实现时,中间操作会构建一个操作链,这些操作会在终端操作触发时依次执行。
  3. 终端操作
    • 终端操作会触发整个 Stream 操作链的执行,是及早求值的。例如 forEachcollect 等操作。
    • 执行时,会从数据源开始,依次执行中间操作链,最后执行终端操作,并返回结果。

在复杂业务场景下的深度优化

  1. 避免重复计算 - 中间结果复用
    • 使用 peek 操作在不改变流元素的情况下记录中间结果。例如,如果有多个 mapfilter 操作,且某些中间结果需要复用,可以在合适位置使用 peek
    • 示例代码:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
AtomicReference<List<Integer>> intermediateResult = new AtomicReference<>();
List<Integer> result = numbers.stream()
       .peek(list -> intermediateResult.set(new ArrayList<>(list)))
       .map(n -> n * 2)
       .filter(n -> n > 5)
       .collect(Collectors.toList());
// 这里可以复用 intermediateResult 中的数据
- 还可以使用 `collect` 操作提前收集中间结果,然后在后续操作中复用。例如,先收集成 `List`,再对这个 `List` 进行其他 Stream 操作。

2. 自定义收集器: - 实现 Collector 接口,该接口有五个方法:supplieraccumulatorcombinerfinishercharacteristics。 - 统计元素出现频率并按频率降序排列

import java.util.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class FrequencyCollector<T> implements Collector<T, Map<T, Integer>, Map<T, Integer>> {

    @Override
    public Supplier<Map<T, Integer>> supplier() {
        return HashMap::new;
    }

    @Override
    public BiConsumer<Map<T, Integer>, T> accumulator() {
        return (map, element) -> map.put(element, map.getOrDefault(element, 0) + 1);
    }

    @Override
    public BinaryOperator<Map<T, Integer>> combiner() {
        return (map1, map2) -> {
            map2.forEach((key, value) -> map1.put(key, map1.getOrDefault(key, 0) + value));
            return map1;
        };
    }

    @Override
    public Function<Map<T, Integer>, Map<T, Integer>> finisher() {
        return map -> map.entrySet().stream()
               .sorted(Map.Entry.<T, Integer>comparingByValue().reversed())
               .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue, LinkedHashMap::new
                ));
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));
    }
}
- 使用自定义收集器示例:
List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana");
Map<String, Integer> frequencyMap = words.stream().collect(new FrequencyCollector<>());
System.out.println(frequencyMap);

通过以上方式,可以深入理解并优化 Java Stream API 在复杂业务场景中的应用。