深入解析 Java Stream API:从 List 到 Map 的优雅转换!!!

🚀 深入解析 Java Stream API:从 ListMap 的优雅转换 🔧

大家好!👋 今天我们来聊聊 Java 8 中一个非常常见的操作:使用 Stream API 将 List 转换为 Map。🎉 具体来说,我们将深入分析以下代码片段:

java 复制代码
Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream()
                .collect(Collectors.toMap(InviteCode::getId, ic -> ic));

这段代码看似简单,但背后涉及了 Stream API、方法引用、Lambda 表达式以及 Collectors.toMap 的强大功能。💡 我们将从代码的背景开始,逐步拆解它的实现原理,探讨使用场景、优势和优化方法,最后通过一个实际案例展示它的应用。为了更直观地理解整个过程,我们还会插入一个 Mermaid 流程图!📊

准备好了吗?让我们开始吧!🚀


📖 背景:为什么需要将 List 转换为 Map

在 Java 开发中,我们经常需要处理集合数据。例如,在一个邀请码系统中,我们有一个 List<InviteCode>,其中 InviteCode 是一个实体类,包含 idinviteCodeinviteLevelcreatedBy 等字段:

java 复制代码
public class InviteCode {
    private Integer id;
    private String inviteCode;
    private Integer inviteLevel;
    private Integer createdBy;

    // Getters and Setters
    public Integer getId() {
        return id;
    }

    public String getInviteCode() {
        return inviteCode;
    }

    public Integer getInviteLevel() {
        return inviteLevel;
    }

    public Integer getCreatedBy() {
        return createdBy;
    }
}

假设我们有一个 List<InviteCode>,包含 adminId = 7 的所有邀请码记录:

id admin_id created_by invite_code invite_level
20 7 NULL ****** 0
21 7 20 263113 1
22 7 20 704358 1
23 7 20 982868 1
24 7 NULL ****** 0
25 7 24 ****** 1
26 7 25 ****** 2
27 7 26 991476 3

我们的目标是构建一个以 adminId 为根的邀请码层级树。为了高效地查找某个 id 对应的 InviteCode 对象,我们需要将 List<InviteCode> 转换为 Map<Integer, InviteCode>,其中:

  • 键(Key)InviteCodeidInteger 类型)。
  • 值(Value)InviteCode 对象本身。

这就是以下代码的作用:

java 复制代码
Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream()
                .collect(Collectors.toMap(InviteCode::getId, ic -> ic));

🌟 代码拆解:一步步理解

让我们逐步拆解这段代码,弄清楚它是如何工作的!

1. inviteCodes.stream()

  • inviteCodes :是一个 List<InviteCode>,包含 adminId = 7 的 8 条记录(id = 20, 21, ..., 27)。
  • stream() :将 List<InviteCode> 转换为一个 Stream<InviteCode>
    • Stream 是 Java 8 引入的流式 API,允许你以声明式的方式处理集合数据(例如映射、过滤、归约等)。

结果inviteCodes.stream() 生成了一个 Stream<InviteCode>,包含 8 个 InviteCode 对象。

2. .collect(Collectors.toMap(...))

  • collect :是 Stream 的终止操作,用于将流中的元素收集到一个结果容器中(例如 ListSetMap)。
  • Collectors.toMap :是一个收集器(Collector),专门用于将流中的元素收集到一个 Map 中。
Collectors.toMap 的方法签名
java 复制代码
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
    Function<? super T, ? extends K> keyMapper,
    Function<? super T, ? extends U> valueMapper
)
  • 参数
    • keyMapper:一个函数,用于从流中的每个元素提取 Map 的键(Key)。
    • valueMapper:一个函数,用于从流中的每个元素提取 Map 的值(Value)。
  • 返回值 :一个 Map<K, U>

在我们的代码中:

  • TInviteCode(流中的元素类型)。
  • KInteger(键的类型)。
  • UInviteCode(值的类型)。

3. InviteCode::getId

  • InviteCode::getId :这是一个方法引用 (Method Reference),等价于 Lambda 表达式 ic -> ic.getId()
  • 作用 :从 InviteCode 对象中提取 id 字段,作为 Map 的键。
  • 类型Function<InviteCode, Integer>,将 InviteCode 映射为 Integer

示例

  • 如果 InviteCode 对象的 id20InviteCode::getId 会返回 20

4. ic -> ic

  • ic -> ic:这是一个 Lambda 表达式,表示一个简单的映射函数。
  • 作用 :将 InviteCode 对象本身作为 Map 的值。
  • 类型Function<InviteCode, InviteCode>,将 InviteCode 映射为它自己。

示例

  • 如果 InviteCode 对象是 icid = 20),ic -> ic 直接返回这个 ic 对象。

5. 整体效果

  • Collectors.toMap(InviteCode::getId, ic -> ic)
    • Stream<InviteCode> 中的每个 InviteCode 对象:
      • 使用 InviteCode::getId 提取 id 作为键。
      • 使用 ic -> ic 提取整个 InviteCode 对象作为值。
    • 将所有键值对收集到一个 Map<Integer, InviteCode> 中。

结果

  • inviteCodeMap 是一个 Map<Integer, InviteCode>,其中:
    • inviteCodeMap.get(20)InviteCode(id=20, inviteCode="******", ...)
    • inviteCodeMap.get(21)InviteCode(id=21, inviteCode="263113", ...)
    • ...
    • inviteCodeMap.get(27)InviteCode(id=27, inviteCode="991476", ...)

📊 Mermaid 流程图:可视化转换过程

为了更直观地理解 List<InviteCode>Map<Integer, InviteCode> 的转换过程,我们使用 Mermaid 流程图来展示:
Start: List<InviteCode>
inviteCodes Stream<InviteCode>
inviteCodes.stream() For each InviteCode in Stream Extract Key:
InviteCode::getId
(e.g., id = 20) Extract Value:
ic -> ic
(e.g., InviteCode object) Key-Value Pair:
(20, InviteCode(id=20, ...)) Collect to Map<Integer, InviteCode>
Collectors.toMap() End: Map<Integer, InviteCode>
inviteCodeMap

  • 流程说明
    1. List<InviteCode> 开始,转换为 Stream<InviteCode>
    2. 对流中的每个 InviteCode 对象:
      • 使用 InviteCode::getId 提取键(id)。
      • 使用 ic -> ic 提取值(InviteCode 对象)。
    3. 将所有键值对收集到 Map<Integer, InviteCode> 中。

🌟 为什么需要 inviteCodeMap

在邀请码系统中,我们的目标是构建一个以 adminId 为根的层级树。为了高效地查找某个 id 对应的 InviteCode 对象,我们需要将 List<InviteCode> 转换为 Map<Integer, InviteCode>

1. 后续代码

java 复制代码
// 找到所有根节点(createdBy = NULL)
List<InviteCode> roots = inviteCodes.stream()
        .filter(ic -> ic.getCreatedBy() == null)
        .collect(Collectors.toList());

// 为每个根节点构建树形结构
List<InviteCodeTreeDTO> trees = new ArrayList<>();
for (InviteCode root : roots) {
    InviteCodeTreeDTO tree = buildTree(root, inviteCodeMap, new HashSet<>());
    trees.add(tree);
}

buildTree 方法中,需要根据 createdBy 查找子节点:

java 复制代码
private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Set<Integer> visited) {
    if (!visited.add(root.getId())) {
        throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());
    }

    InviteCodeTreeDTO node = new InviteCodeTreeDTO();
    node.setId(root.getId());
    node.setInviteCode(root.getInviteCode());
    node.setInviteLevel(root.getInviteLevel());
    node.setChildren(new ArrayList<>());

    // 查找所有子节点(createdBy = root.id)
    List<InviteCode> children = inviteCodeMap.values().stream()
            .filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId()))
            .collect(Collectors.toList());

    for (InviteCode child : children) {
        InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, new HashSet<>(visited));
        node.getChildren().add(childNode);
    }

    return node;
}
  • 为什么用 Map
    • 如果直接遍历 inviteCodes 查找子节点(createdBy = root.id),时间复杂度是 O(n)
    • 使用 inviteCodeMap,可以通过 id 直接查找 InviteCode 对象,时间复杂度是 O(1)(尽管当前 children 查找仍需优化)。

🚀 优势:为什么使用 Stream API?

1. 代码简洁

  • Stream API 提供了声明式的写法,比传统的 for 循环更简洁。

  • 传统写法可能需要手动遍历和填充 Map

    java 复制代码
    Map<Integer, InviteCode> inviteCodeMap = new HashMap<>();
    for (InviteCode ic : inviteCodes) {
        inviteCodeMap.put(ic.getId(), ic);
    }
  • 使用 Stream API,代码更简洁优雅。

2. 功能强大

  • Stream API 支持链式操作,可以轻松添加过滤、映射等操作。

  • 例如,如果只想收集 inviteLevel > 0InviteCode

    java 复制代码
    Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream()
            .filter(ic -> ic.getInviteLevel() > 0)
            .collect(Collectors.toMap(InviteCode::getId, ic -> ic));

3. 并行处理

  • Stream API 支持并行处理(parallelStream()),在大规模数据下可以提高性能:

    java 复制代码
    Map<Integer, InviteCode> inviteCodeMap = inviteCodes.parallelStream()
            .collect(Collectors.toMap(InviteCode::getId, ic -> ic));

🛠️ 优化建议

1. 更高效的子节点查找

当前 buildTree 方法中,查找子节点的方式可以通过 inviteCodeMap 进一步优化:

java 复制代码
// 预先构建 createdBy 到子节点的映射
Map<Integer, List<InviteCode>> childrenMap = inviteCodes.stream()
        .filter(ic -> ic.getCreatedBy() != null)
        .collect(Collectors.groupingBy(InviteCode::getCreatedBy));

// 修改 buildTree 方法
private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Map<Integer, List<InviteCode>> childrenMap, Set<Integer> visited) {
    if (!visited.add(root.getId())) {
        throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());
    }

    InviteCodeTreeDTO node = new InviteCodeTreeDTO();
    node.setId(root.getId());
    node.setInviteCode(root.getInviteCode());
    node.setInviteLevel(root.getInviteLevel());
    node.setChildren(new ArrayList<>());

    // 查找所有子节点(createdBy = root.id)
    List<InviteCode> children = childrenMap.getOrDefault(root.getId(), Collections.emptyList());

    for (InviteCode child : children) {
        InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, childrenMap, new HashSet<>(visited));
        node.getChildren().add(childNode);
    }

    return node;
}
  • 效果 :通过 childrenMap,可以以 O(1) 的时间复杂度找到某个 id 的所有子节点。

2. 处理键冲突

Collectors.toMap 默认情况下,如果有重复的键(id),会抛出 IllegalStateException: Duplicate key。在我们的场景中,id 是主键,应该不会有重复,但为了安全起见,可以指定合并策略:

java 复制代码
Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream()
        .collect(Collectors.toMap(
                InviteCode::getId,
                ic -> ic,
                (existing, replacement) -> existing // 如果有重复的 id,保留第一个
        ));
  • 效果 :如果 id 重复,保留第一个 InviteCode 对象。

📝 完整代码:实际应用

以下是完整的 InviteCodeService 实现,展示了如何使用 inviteCodeMap 构建层级树:

java 复制代码
public class InviteCodeService {
    private final InviteCodeRepository inviteCodeRepository;

    public InviteCodeService(InviteCodeRepository inviteCodeRepository) {
        this.inviteCodeRepository = inviteCodeRepository;
    }

    public AdminInviteCodeTreeDTO getAdminInviteCodeTree(Integer adminId) {
        List<InviteCode> inviteCodes = inviteCodeRepository.findByAdminId(adminId);
        if (inviteCodes.isEmpty()) {
            AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
            result.setAdminId(adminId);
            result.setChildren(Collections.emptyList());
            return result;
        }

        // 将 List<InviteCode> 转换为 Map<Integer, InviteCode>
        Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream()
                .collect(Collectors.toMap(InviteCode::getId, ic -> ic));

        // 预构建 createdBy 到子节点的映射
        Map<Integer, List<InviteCode>> childrenMap = inviteCodes.stream()
                .filter(ic -> ic.getCreatedBy() != null)
                .collect(Collectors.groupingBy(InviteCode::getCreatedBy));

        // 找到所有根节点(createdBy = NULL)
        List<InviteCode> roots = inviteCodes.stream()
                .filter(ic -> ic.getCreatedBy() == null)
                .collect(Collectors.toList());

        // 为每个根节点构建树形结构
        List<InviteCodeTreeDTO> trees = new ArrayList<>();
        for (InviteCode root : roots) {
            InviteCodeTreeDTO tree = buildTree(root, inviteCodeMap, childrenMap, new HashSet<>());
            trees.add(tree);
        }

        AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
        result.setAdminId(adminId);
        result.setChildren(trees);
        return result;
    }

    private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Map<Integer, List<InviteCode>> childrenMap, Set<Integer> visited) {
        if (!visited.add(root.getId())) {
            throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());
        }

        InviteCodeTreeDTO node = new InviteCodeTreeDTO();
        node.setId(root.getId());
        node.setInviteCode(root.getInviteCode());
        node.setInviteLevel(root.getInviteLevel());
        node.setChildren(new ArrayList<>());

        List<InviteCode> children = childrenMap.getOrDefault(root.getId(), Collections.emptyList());
        for (InviteCode child : children) {
            InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, childrenMap, new HashSet<>(visited));
            node.getChildren().add(childNode);
        }

        return node;
    }
}

🎉 总结

通过 Stream API 和 Collectors.toMap,我们可以轻松地将 List<InviteCode> 转换为 Map<Integer, InviteCode>,为后续的层级树构建提供了高效的数据结构。💻

  • 核心代码inviteCodes.stream().collect(Collectors.toMap(InviteCode::getId, ic -> ic)) 将列表转换为映射。
  • 优势:代码简洁、功能强大、支持并行处理。
  • 优化 :通过 childrenMap 提高子节点查找效率,处理键冲突。

希望这篇博客对你理解 Stream API 和 Collectors.toMap 有所帮助!💬 如果你有其他问题,欢迎留言讨论!🚀

📚 参考 :Java 官方文档、Collectors 源码。点赞和分享哦!😊

相关推荐
缺点内向1 天前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅1 天前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看1 天前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程1 天前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t1 天前
ZIP工具类
java·zip
lang201509281 天前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan1 天前
第10章 Maven
java·maven
百锦再1 天前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说1 天前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多1 天前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring