0

0

Java Stream API:高效提取嵌套集合中的唯一元素

霞舞

霞舞

发布时间:2025-10-09 09:19:00

|

294人浏览过

|

来源于php中文网

原创

Java Stream API:高效提取嵌套集合中的唯一元素

本教程详细介绍了如何使用Java Stream API,特别是flatMap()和mapMulti()方法,从包含嵌套列表的数据结构中高效提取唯一的元素并将其收集到Set中。通过具体的Employee和Address类示例,展示了如何将传统的多层循环转换为简洁、声明式的Stream操作,从而提升代码的可读性和维护性。

在现代java开发中,处理集合数据是日常任务。当数据结构涉及嵌套集合时,例如一个包含员工列表,而每个员工又包含多个地址列表的场景,如何高效且优雅地提取特定信息(如所有唯一的城市名称)就成为了一个常见挑战。java stream api提供了强大的工具来解决这类问题,特别是flatmap()和mapmulti()操作符,它们能够将多层嵌套的集合“扁平化”为单一的流,从而简化后续的数据处理。

1. 数据模型定义

首先,我们定义本教程中将使用的Employee和Address类,它们代表了典型的嵌套数据结构:

import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class EmployeeDataProcessor {

    public static class Address {
        private String city;

        public Address(String city) {
            this.city = city;
        }

        public String getCity() {
            return city;
        }

        // 可以添加equals和hashCode方法以确保Address对象的唯一性,但此处我们只关心city字符串的唯一性
        @Override
        public String toString() {
            return "Address{" + "city='" + city + '\'' + '}';
        }
    }

    public static class Employee {
        private String name;
        private List
addresses; public Employee(String name, List
addresses) { this.name = name; this.addresses = addresses; } public List
getAddresses() { return addresses; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", addresses=" + addresses + '}'; } } // 示例数据 public static List getSampleEmployees() { return List.of( new Employee("Alice", List.of(new Address("New York"), new Address("London"))), new Employee("Bob", List.of(new Address("London"), new Address("Paris"))), new Employee("Charlie", List.of(new Address("New York"), new Address("Tokyo"))) ); } }

2. 传统方法回顾

在Java Stream API出现之前,要从上述结构中提取所有唯一的城市名称,通常需要使用嵌套的for循环,代码如下:

public static Set getCityUniqueNameTraditional(List empList) {
    Set cityUniqueNames = new HashSet<>();
    for (Employee e : empList) {
        List
addList = e.getAddresses(); for (Address add : addList) { cityUniqueNames.add(add.getCity()); } } return cityUniqueNames; }

这种方法虽然直观,但在处理更复杂的转换逻辑时,代码会变得冗长且难以维护。

3. Stream API 解决方案

Java Stream API提供了一种更声明式、更简洁的方式来处理集合数据。对于扁平化嵌套集合的需求,flatMap()和mapMulti()是关键操作符。

立即学习Java免费学习笔记(深入)”;

3.1 方法一:使用 flatMap()

flatMap()操作符是Stream API中用于扁平化(flattening)流的核心。它将流中的每个元素映射为一个新的流,然后将所有这些新的流连接成一个单一的流。这正是解决“列表的列表”问题的理想工具。

实现步骤与代码示例:

Cursor
Cursor

一个新的IDE,使用AI来帮助您重构、理解、调试和编写代码。

下载
  1. 从Employee列表创建一个Stream。
  2. 使用flatMap()将每个Employee对象映射为其包含的Address列表的Stream。
  3. 对扁平化后的Address流,使用map()提取每个Address对象的城市名称。
  4. 使用collect(Collectors.toSet())将所有唯一的城市名称收集到一个Set中。
public static Set getCityUniqueNameWithFlatMap(List empList) {
    return empList.stream()
        // 将Stream扁平化为Stream
// 对于每个Employee,获取其地址列表,并将其转换为一个Stream .flatMap(employee -> employee.getAddresses().stream()) // 从Stream
中提取城市名称,得到Stream .map(Address::getCity) // 将所有唯一的城市名称收集到一个Set中 .collect(Collectors.toSet()); }

工作原理分析:

  • empList.stream():创建了一个Stream
  • .flatMap(employee -> employee.getAddresses().stream()):这是核心步骤。对于流中的每个Employee对象,employee.getAddresses()返回一个List
    。.stream()将其转换为Stream
    。flatMap()接收这些内部的Stream
    ,并将它们合并成一个统一的Stream
  • .map(Address::getCity):现在我们有了一个包含所有地址的扁平化流。map()操作符将每个Address对象转换为其对应的城市名称字符串,生成Stream
  • .collect(Collectors.toSet()):最后,Collectors.toSet()是一个终端操作,它将流中的所有元素收集到一个Set中。Set的特性保证了最终结果中城市名称的唯一性。

3.2 方法二:使用 mapMulti() (Java 16+)

mapMulti()是Java 16引入的一个新操作符,它提供了比flatMap()更灵活和可能更高效的扁平化机制。mapMulti()接收一个BiConsumer,该BiConsumer的第一个参数是当前流的元素,第二个参数是一个Consumer,用于“提供”零个、一个或多个元素给下游流。

实现步骤与代码示例:

  1. 从Employee列表创建一个Stream。
  2. 使用mapMulti()将每个Employee对象及其地址列表的元素逐个“提供”给下游流。
  3. 对扁平化后的Address流,使用map()提取城市名称。
  4. 使用collect(Collectors.toSet())收集唯一的城市名称。
public static Set getCityUniqueNameWithMapMulti(List empList) {
    return empList.stream()
        // 使用mapMulti将Stream扁平化为Stream
//
是类型提示,告诉编译器BiConsumer将生成Address类型的元素 .
mapMulti((employee, addressConsumer) -> // 对于每个Employee,遍历其地址列表,并将每个地址提供给addressConsumer employee.getAddresses().forEach(addressConsumer) ) // 从Stream
中提取城市名称,得到Stream .map(Address::getCity) // 将所有唯一的城市名称收集到一个Set中 .collect(Collectors.toSet()); }

工作原理分析:

  • empList.stream():创建Stream
  • .
    mapMulti((employee, addressConsumer) -> employee.getAddresses().forEach(addressConsumer)):这是mapMulti()的核心。
    • 是一个类型提示,表明mapMulti将产生Address类型的元素。
    • BiConsumer的第一个参数employee是当前流中的Employee对象。
    • 第二个参数addressConsumer是一个Consumer
      。我们通过调用employee.getAddresses().forEach(addressConsumer),将当前Employee的所有Address对象逐个传递给addressConsumer。每当addressConsumer被调用一次,一个Address对象就会被“提供”给下游流。
  • .map(Address::getCity) 和 .collect(Collectors.toSet()):后续操作与flatMap()示例相同。

4. flatMap 与 mapMulti 的选择与考量

  • flatMap()
    • 优点:更早引入,更广为人知,语义清晰(将流的流扁平化)。对于简单的扁平化场景,代码通常更简洁。
    • 缺点:每次映射到一个集合时,都需要创建一个新的内部Stream(例如e.getAddress().stream()),这可能在某些性能敏感的场景下引入轻微的开销。
  • mapMulti()
    • 优点
      • 性能优化:避免了为每个内部集合创建单独的Stream对象,通过直接将元素“提供”给下游Consumer,减少了对象创建和垃圾回收的压力,可能在处理大量数据时提供更好的性能。
      • 灵活性:BiConsumer允许更复杂的逻辑,例如根据条件选择性地提供元素,或者提供与输入元素类型完全不同的元素。
    • 缺点
      • Java版本要求:需要Java 16或更高版本。
      • 学习曲线:BiConsumer和“提供”元素的模式对于初学者来说可能不如flatMap直观。

在大多数日常使用场景中,flatMap()已经足够高效且易于理解。如果你正在使用Java 16或更高版本,并且对性能有极致要求,或者需要更精细地控制扁平化过程(例如,基于某些条件跳过某些元素的提供),那么mapMulti()是一个值得考虑的强大替代方案。

5. 注意事项

  • 空值处理:在实际应用中,employee.getAddresses()可能返回null,或者返回一个空的地址列表。
    • 如果返回null,直接调用.stream()会抛出NullPointerException。可以使用Optional或者在flatMap / mapMulti中添加null检查,例如Optional.ofNullable(employee.getAddresses()).orElse(Collections.emptyList()).stream()。
    • 如果返回空列表,stream()或forEach()操作会正常执行,但不会产生任何元素,不会影响最终结果。
  • 性能考量:对于非常大的数据集,Stream操作的性能可能会受到JVM优化、垃圾回收以及具体操作符实现的影响。通常,Stream API的性能与传统循环相当,有时甚至更好。
  • 可读性:Stream API旨在提高代码的可读性和表达力。过度复杂的Stream链可能会适得其反,此时可能需要考虑将逻辑拆分为多个方法。

6. 总结

通过本教程,我们学习了如何利用Java Stream API中的flatMap()和mapMulti()操作符,高效且优雅地从嵌套集合中提取唯一的元素。flatMap()提供了一种简洁的扁平化方式,而mapMulti()(Java 16+)则在性能和灵活性方面提供了更高级的选项。掌握这些技术,可以显著提升处理复杂集合数据时的代码质量和开发效率。在选择使用哪种方法时,应综合考虑项目的Java版本、性能要求以及代码的可读性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

483

2023.08.02

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

237

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

458

2024.03.01

php中foreach用法
php中foreach用法

本专题整合了php中foreach用法的相关介绍,阅读专题下面的文章了解更多详细教程。

76

2025.12.04

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

320

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1502

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

625

2023.11.24

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 8万人学习

Java 教程
Java 教程

共578课时 | 53.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号