跳转到主内容
极星编程网:以代码为星,赴技术山海!

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)流的核心。它将流中的每个元素映射为一个新的流,然后将所有这些新的流连接成一个单一的流。这正是解决“列表的列表”问题的理想工具。 实现步骤与代码示例: Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 从Employee列表创建一个Stream。 使用flatMap()将每个Employee对象映射为其包含的Address列表的Stream。 对扁平化后的Address流,使用map()提取每个Address对象的城市名称。 使用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,用于“提供”零个、一个或多个元素给下游流。 实现步骤与代码示例: 从Employee列表创建一个Stream。 使用mapMulti()将每个Employee对象及其地址列表的元素逐个“提供”给下游流。 对扁平化后的Address流,使用map()提取城市名称。 使用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版本、性能要求以及代码的可读性。

相关文章