326. Java Stream API - 实现自定义的 toList() 与 toSet() 收集器

326. Java Stream API - 实现自定义的 toList()toSet() 收集器

在 Java 的 Stream API 中,Collectors.toList()Collectors.toSet() 是最常见的两个内置收集器。但你是否好奇它们背后的原理?今天我们就带大家手动实现一个行为等同于 toList() 的收集器 ,并了解如何基于它改造为 toSet() 收集器。


📦 实现一个自定义 toList() 收集器

java 复制代码
class ToList<T> implements Collector<T, List<T>, List<T>> {

    @Override
    public Supplier<List<T>> supplier() {
        return ArrayList::new; // 创建一个空的 ArrayList 作为中间容器
    }

    @Override
    public BiConsumer<List<T>, T> accumulator() {
        return Collection::add; // 将元素累加到 List 中
    }

    @Override
    public BinaryOperator<List<T>> combiner() {
        return (list1, list2) -> {
            list1.addAll(list2); // 合并两个 List(用于并行流)
            return list1;
        };
    }

    @Override
    public Function<List<T>, List<T>> finisher() {
        return Function.identity(); // 直接返回中间容器,不需要额外转换
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Set.of(Characteristics.IDENTITY_FINISH); // 说明 finisher 是 identity
    }
}

🚀 使用我们的 ToList 收集器

java 复制代码
Collection<String> strings = List.of("one", "two", "three", "four", "five");

List<String> result = strings.stream()
    .collect(new ToList<>()); // 使用我们自定义的收集器

System.out.println("result = " + result);

💡 输出结果:

java 复制代码
result = [one, two, three, four, five]

🔄 将其改造成 toSet() 收集器

我们只需要修改两处,就能实现一个等价于 Collectors.toSet() 的收集器:

✅ 修改 1:使用 HashSet 作为容器

java 复制代码
public Supplier<Set<T>> supplier() {
    return HashSet::new;
}

✅ 修改 2:声明该收集器是无序的

java 复制代码
public Set<Characteristics> characteristics() {
    return Set.of(
        Characteristics.IDENTITY_FINISH,
        Characteristics.UNORDERED // 不保证处理顺序
    );
}

🧪 ToSet 收集器完整实现示例

java 复制代码
class ToSet<T> implements Collector<T, Set<T>, Set<T>> {

    @Override
    public Supplier<Set<T>> supplier() {
        return HashSet::new;
    }

    @Override
    public BiConsumer<Set<T>, T> accumulator() {
        return Set::add;
    }

    @Override
    public BinaryOperator<Set<T>> combiner() {
        return (set1, set2) -> {
            set1.addAll(set2);
            return set1;
        };
    }

    @Override
    public Function<Set<T>, Set<T>> finisher() {
        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Set.of(
            Characteristics.IDENTITY_FINISH,
            Characteristics.UNORDERED
        );
    }
}

🎯 总结一下关键点

元素 toList() toSet()
容器类型 ArrayList HashSet
是否无序 ❌(有序) ✅(无序)
特性声明 IDENTITY_FINISH IDENTITY_FINISH, UNORDERED

🧠 小贴士

  • 想提升性能? 在能接受无序的场景中使用 UNORDERED
  • 想避免不必要的转换?AR 类型一致时,记得声明 IDENTITY_FINISH
  • 并行流支持? 如果想支持并行执行,还可以考虑加入 CONCURRENT 特性(配合线程安全结构)。
相关推荐
逝水无殇16 分钟前
C# 异常处理详解
开发语言·后端·c#
kyriewen30 分钟前
看了微软几万人用AI编程的数据——效率涨24%的代价没人提
前端·ai编程·claude
2601_9637713732 分钟前
How to Scale Your WordPress Store Traffic in 2026
前端·php·plugin
亿元程序员41 分钟前
老板说我的3D箭头游戏用来做试玩太普通了,没人想玩,让我变点花样...
前端
李伟_Li慢慢1 小时前
01-threejs架构原理-课程简介
前端·three.js
考虑考虑3 小时前
Sentinel安装
java·后端·微服务
_瑞3 小时前
深入理解 iOS 渲染原理
前端·ios
IT_陈寒3 小时前
SpringBoot自动配置失灵?你可能忘了这个关键注解
前端·人工智能·后端
碎碎念_4923 小时前
SpringBoot + Vue 前后端分离从 0 到 1 完整环境配置流程
vue.js·spring boot·后端
逝水无殇3 小时前
C# 文件的输入与输出详解
开发语言·数据库·后端·c#