当前位置:

首页 > 编程开发 > Java Stream Collector 高级用法:定制化累加器与收集器实现

Java Stream Collector 高级用法:定制化累加器与收集器实现

本文深入探讨了JavaStreamAPI中Collector的高级定制化实现,重点讲解了如何灵活选择和设计累加器类型(A),以及如何使用Collector.of()方法构建自定义收集器。通过具体示例,展示了利用基本类型数组、现有可变类型、匿名内部类等作为累加器,有效避免了不必要的类创建,提升了代码的简洁性和灵活性。

本文目录

    Java Stream Collector 高级用法:定制化累加器与收集器实现

    本文深入探讨了 Java Stream API 中 Collector 的高级定制化实现,重点讲解了如何灵活选择和设计累加器类型 (A),以及如何使用 Collector.of() 方法构建自定义收集器。通过具体示例,展示了利用基本类型数组、现有可变类型、匿名内部类等作为累加器,有效避免了不必要的类创建,提升了代码的简洁性和灵活性。

    理解 Collector 接口及其泛型

    java.util.stream.Collector 接口是 Java Stream API 中一个核心组件,用于将流中的元素聚合成一个结果。它定义了三种泛型类型:

    • T:流中元素的类型。
    • A:累加器(accumulator)的类型,这是一个可变的中间结果类型,用于在收集过程中存储状态。它通常是实现细节,不对外暴露。
    • R:最终结果的类型,即收集操作完成后返回的类型。

    要创建一个自定义的 Collector,最常用的方法是使用 Collector.of() 工厂方法,它需要四个核心函数式接口作为参数:

    1. supplier: 一个 Supplier,用于创建新的累加器实例。
    2. accumulator: 一个 BiConsumer,用于将流中的单个元素 T 合并到累加器 A 中。
    3. combiner: 一个 BinaryOperator,用于将两个累加器 A 合并成一个(在并行流中特别有用)。
    4. finisher: 一个 Function,用于将最终的累加器 A 转换成最终结果 R。

    一个常见的误解是,累加器类型 A 必须是一个独立的、专门为此目的创建的具名类,并且其方法(如 supply、accumulate、combine、finish)需要直接在 Collector 的实现中引用该类的静态方法或实例方法。实际上,Collector.of() 的函数参数提供了极大的灵活性,允许我们使用各种方式来定义这些行为,而无需强制创建额外的具名类。

    灵活选择累加器类型

    累加器类型 A 的选择是实现自定义 Collector 的关键。它不一定非得是一个复杂的自定义类,可以是以下几种类型:

    1. 使用基本类型数组作为累加器

    对于简单的数值累加,一个单元素的基本类型数组(如 int[])是一个非常高效且简洁的累加器。它提供了可变性,并且避免了装箱拆箱的开销。

    示例:计算流中整数的和

    import java.util.stream.Collector;
    import java.util.stream.Stream;
    
    public class CustomSumCollector {
    
        /**
         * 创建一个收集器,用于计算整数流的总和。
         * 累加器类型为 int[1],其中数组的第一个元素存储当前和。
         *
         * @return 收集整数到其总和的收集器。
         */
        public static Collector sum() {
            return Collector.of(
                () -> new int[1], // supplier: 创建一个长度为1的int数组作为累加器
                (a, i) -> a[0] += i, // accumulator: 将元素i加到数组的第一个元素
                (a, b) -> { a[0] += b[0]; return a; }, // combiner: 合并两个累加器
                a -> a[0], // finisher: 返回数组的第一个元素作为最终结果
                Collector.Characteristics.UNORDERED // 标识收集器不关心元素顺序
            );
        }
    
        public static void main(String[] args) {
            Stream numbers = Stream.of(1, 2, 3, 4, 5);
            Integer totalSum = numbers.collect(sum());
            System.out.println("Sum using custom collector: " + totalSum); // Output: Sum using custom collector: 15
        }
    }

    在这个例子中,int[1] 作为累加器 A,简洁地完成了累加任务。

    2. 使用现有可变类型作为累加器

    Java 标准库中提供了许多可变的类,它们可以直接用作累加器,特别是当需要考虑线程安全时。

    示例:使用 AtomicInteger 计算流中整数的和(并发安全)

    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.stream.Collector;
    import java.util.stream.Stream;
    
    public class ConcurrentSumCollector {
    
        /**
         * 创建一个并发安全的收集器,用于计算整数流的总和。
         * 累加器类型为 AtomicInteger。
         *
         * @return 并发收集整数到其总和的收集器。
         */
        public static Collector concurrentSum() {
            return Collector.of(
                AtomicInteger::new, // supplier: 创建一个新的 AtomicInteger 实例
                AtomicInteger::addAndGet, // accumulator: 将元素i原子地加到 AtomicInteger 中
                (a, b) -> { a.addAndGet(b.intValue()); return a; }, // combiner: 合并两个 AtomicInteger
                AtomicInteger::intValue, // finisher: 返回 AtomicInteger 的值作为最终结果
                Collector.Characteristics.UNORDERED,
                Collector.Characteristics.CONCURRENT // 标识收集器支持并发
            );
        }
    
        public static void main(String[] args) {
            Stream numbers = Stream.of(1, 2, 3, 4, 5);
            Integer totalSum = numbers.collect(concurrentSum());
            System.out.println("Concurrent sum using custom collector: " + totalSum); // Output: Concurrent sum using custom collector: 15
        }
    }

    AtomicInteger 天然提供了线程安全的原子操作,非常适合在并行流中使用。

    3. 使用匿名内部类或 Ad-hoc 类型作为累加器

    当没有合适的现有类型,并且不想创建新的具名类时,可以使用匿名内部类或 Object 的子类(通常通过 new Object() {} 语法)作为累加器。这种方式将累加器的定义局部化在 Collector 的实现内部,增加了封装性。

    示例:收集 Map.Entry 流中具有最大值的键列表

    假设我们有一个 Map.Entry 的流,我们希望找出所有具有最大值的键,并以 List 的形式返回。

    方法一:使用 AbstractMap.SimpleEntry 作为累加器

    AbstractMap.SimpleEntry 可以作为一个简单的键值对容器,这里我们用它来存储当前找到的最大值 (Integer) 和对应的键列表 (List)。

    import java.util.*;
    import java.util.stream.Collector;
    import java.util.stream.Stream;
    
    public class KeysToMaximumCollector {
    
        /**
         * 收集 Map.Entry 流中具有最大值的键列表。
         * 累加器类型为 AbstractMap.SimpleEntry, Integer>。
         *
         * @param  键的类型
         * @return 收集器,返回具有最大值的键列表。
         */
        public static  Collector, ?, List> keysToMaximum() {
            return Collector.of(
                () -> new AbstractMap.SimpleEntry<>(new ArrayList<>(), Integer.MIN_VALUE), // supplier: 初始累加器
                (current, next) -> { // accumulator: 处理每个 Map.Entry
                    int max = current.getValue();
                    int value = next.getValue();
                    if (value >= max) {
                        if (value > max) { // 找到更大的值,清空旧列表,更新最大值
                            current.setValue(value);
                            current.getKey().clear();
                        }
                        current.getKey().add(next.getKey()); // 添加当前键
                    }
                },
                (a, b) -> { // combiner: 合并两个累加器
                    int maxA = a.getValue();
                    int maxB = b.getValue();
                    if (maxA < maxB) return b; // B 的最大值更大,返回 B
                    if (maxA == maxB) a.getKey().addAll(b.getKey()); // 最大值相同,合并键列表
                    return a; // 否则返回 A
                },
                Map.Entry::getKey // finisher: 返回累加器中的键列表
            );
        }
    
        public static void main(String[] args) {
            Map map = new HashMap<>();
            map.put("A", 10);
            map.put("B", 20);
            map.put("C", 15);
            map.put("D", 20);
            map.put("E", 5);
    
            List keys = map.entrySet().stream().collect(keysToMaximum());
            System.out.println("Keys with maximum value: " + keys); // Output: Keys with maximum value: [B, D] (顺序可能不同)
        }
    }

    方法二:使用匿名内部类作为累加器

    这种方式创建了一个只在 Collector 内部可见的、具有特定字段的匿名对象作为累加器。

    import java.util.*;
    import java.util.stream.Collector;
    import java.util.stream.Stream;
    
    public class KeysToMaximumAdHocCollector {
    
        /**
         * 收集 Map.Entry 流中具有最大值的键列表。
         * 累加器类型为匿名内部类。
         *
         * @param  键的类型
         * @return 收集器,返回具有最大值的键列表。
         */
        public static  Collector, ?, List> keysToMaximum() {
            return Collector.of(
                () -> new Object() { // supplier: 创建一个匿名内部类实例作为累加器
                    int max = Integer.MIN_VALUE;
                    final List keys = new ArrayList<>();
                },
                (current, next) -> { // accumulator: 处理每个 Map.Entry
                    int value = next.getValue();
                    if (value >= current.max) {
                        if (value > current.max) {
                            current.max = value;
                            current.keys.clear();
                        }
                        current.keys.add(next.getKey());
                    }
                },
                (a, b) -> { // combiner: 合并两个匿名内部类累加器
                    if (a.max < b.max) return b;
                    if (a.max == b.max) a.keys.addAll(b.keys);
                    return a;
                },
                a -> a.keys // finisher: 返回累加器中的键列表
            );
        }
    
        public static void main(String[] args) {
            Map map = new HashMap<>();
            map.put("A", 10);
            map.put("B", 20);
            map.put("C", 15);
            map.put("D", 20);
            map.put("E", 5);
    
            List keys = map.entrySet().stream().collect(keysToMaximum());
            System.out.println("Keys with maximum value (ad-hoc): " + keys); // Output: Keys with maximum value (ad-hoc): [B, D] (顺序可能不同)
        }
    }

    这两种方法都避免了为累加器单独创建一个具名类,使代码更加紧凑和局部化。

    注意事项与总结

    1. 累加器类型的选择:

    2. 函数式接口的实现:

    3. Collector.Characteristics:

    通过灵活运用 Collector.of() 方法,并根据实际需求选择合适的累加器类型,开发者可以创建出高效、简洁且功能强大的自定义收集器,从而更好地利用 Java Stream API 的能力。无需为每个自定义收集器都创建独立的累加器类,这大大简化了代码结构和维护成本。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    如何关闭Visual Studio2022启动时自动加载上次项目
    如何关闭Visual Studio2022启动时自动加载上次项目

    Visual Studio 2022 默认会在启动时自动恢复上次关闭时的解决方案,这在频繁切换项目或临时查看代码时可能造成干扰。本文将指导你如何通过“工具 > 选项”菜单,将启动行为更改为显示启动窗口或空环境,帮助你掌握启动主动权,同时澄清该设置与“启动项目”及最近文件记录的区别。

    GitHub六个AI开源项目能做什么?软件理解、语音与求职场景
    GitHub六个AI开源项目能做什么?软件理解、语音与求职场景

    解析六个GitHub AI开源项目的具体用途,包括软件静态分析、智能体回答检查、多语言配音、求职材料生成、本地代码检索及动态歌词展示,帮助开发者根据实际需求筛选工具并正确验证结果。

    opencode安装skills教程及本地配置方法
    opencode安装skills教程及本地配置方法

    遇到OpenCode Skills无法加载或配置无效?本文手把手教你完成Skills的安装、环境变量设置及本地调试,避开常见坑点,让AI助手更懂你的代码习惯。

    如何开启Visual Studio2022实时代码分析警告提示
    如何开启Visual Studio2022实时代码分析警告提示

    解决 Visual Studio 2022 编辑时不显示代码分析警告的问题。指南涵盖 .NET 项目实时分析设置、Roslyn 分析器范围调整及错误列表来源筛选,确保开发者能高效利用实时代码诊断功能。

    如何在Visual Studio2022查看变量监视窗口调试程序
    如何在Visual Studio2022查看变量监视窗口调试程序

    详解 Visual Studio 2022 调试技巧:包括断点设置、监视窗口开启方法、变量添加步骤以及通过 F10 单步执行观察变量值变化的全过程。

    Visual Studio2017设置默认编码为UTF-8不带BOM
    Visual Studio2017设置默认编码为UTF-8不带BOM

    针对 Visual Studio 2017 缺乏全局默认编码选项的情况,详解如何利用 .editorconfig 设定项目级 UTF-8 无 BOM 规则,以及手动转换现有文件并校验字节的方法。

    Visual Studio2022F5调试提示启动项目未配置怎么修复
    Visual Studio2022F5调试提示启动项目未配置怎么修复

    解决 Visual Studio 2022 F5 调试报错问题,通过设置启动项目、检查解决方案属性及正确区分类库与应用项目,快速恢复调试功能。

    Visual Studio2022NuGet包还原失败离线处理方案
    Visual Studio2022NuGet包还原失败离线处理方案

    解决Visual Studio 2022离线环境中NuGet包还原失败的难题,涵盖依赖链分析、本地源配置、包集收集及隔离验证步骤,帮助开发者构建稳定的离线开发环境。

    Visual Studio2017大项目加载缓慢,优化加载性能技巧
    Visual Studio2017大项目加载缓慢,优化加载性能技巧

    解决Visual Studio 2017大型项目加载慢的实用指南。涵盖性能监测、扩展管理、文档恢复优化及版本兼容性检查,帮助开发者精准定位瓶颈并提升工作效率。

    Visual Studio2022设置多启动项目同时调试运行
    Visual Studio2022设置多启动项目同时调试运行

    详解 Visual Studio 2022 多启动项目配置方法,包括启动动作选择、顺序调整及调试技巧,帮助开发者高效调试分布式或前后端分离应用。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。