17370845950

标题:Java 中使用 Stream 和递归 Map 实现嵌套字符串的层级分组

本文介绍如何将形如 "caso.id"、"caso.responsavel.dadospessoais.nome" 的点分隔字符串列表,递归构建为嵌套的 `map` 结构,实现真正的多级分组与树状打印,适用于动态字段建模、json schema 生成等场景。

在 Java 中,对点号(.)分隔的路径字符串进行层级化组织是常见需求,例如映射 DTO 字段路径、构建动态查询条件树或生成嵌套配置结构。Collectors.groupingBy() 虽强大,但仅支持单层分组;而本例需递归解析路径深度、动态创建嵌套 Map,无法通过标准 Stream 操作一步完成——必须结合递归逻辑与泛型 Map 构建。

核心思路是:将每个字符串按 . 拆分为路径节点数组(如 "caso.responsavel.dadosPessoais.nome" → ["caso", "responsavel", "dadosPessoais", "nome"]),再逐层下沉插入到嵌套 Map 中。其中 Object 类型可容纳两种值:

  • 叶子节点(最后一级)→ 实际无值,仅作占位(或可存 null/Boolean.TRUE 表示存在);
  • 中间节点 → 指向子 Map

以下是完整可运行实现:

import java.util.*;

public class NestedStringGrouping {

    public static void main(String[] args) {
        String[] array = {
            "caso.id",
            "caso.unidadeDoCaso.id",
            "caso.etiqueta",
            "caso.sigiloso",
            "caso.idPecaSegredoJustica",
            "caso.numeroAno",
            "caso.numero",
            "caso.competencia.id",
            "caso.competencia.ativo",
            "caso.competencia.nome",
            "caso.responsavel.id",
            "caso.responsavel.dadosPessoais.nome",
            "caso.escrivao.id",
            "caso.escrivao.dadosPessoais.nome"
        };

        Map root = new HashMap<>();
        for (String path : array) {
            String[] nodes = path.split("\\.");
            fill(root, nodes, 0);
        }

        print(root, "");
    }

    /**
     * 递归填充嵌套 Map:从 index 开始,将 nodes[index..end] 插入到 map 中
     */
    public static void fill(Map map, String[] nodes, int index) {
        if (index >= nodes.length) return;

        String key = nodes[index];
        Object existing = map.get(key);

        if (existing == null) {
            // 当前层级不存在 → 创建新子 Map 并挂载
            Map childMap = new HashMap<>();
            map.put(key, childMap);
            fill(childMap, nodes, index + 1); // 继续向下构建
        } else if (existing instanceof Map) {
            // 已存在子 Map → 递归插入到该子 Map
            @SuppressWarnings("unchecked")
            Map childMap = (Map) existing;
            fill(childMap, nodes, index + 1);
        }
        // 若 existing 非 Map(如意外覆盖),此处可抛异常或忽略(本例中不会发生)
    }

    /**
     * 树状打印 Map,缩进表示层级深度
     */
    public static void print(Map map, String indent) {
        for (String key : map.keySet()) {
            System.out.println(indent + key);
            Object value = map.get(key);
            if (value instanceof Map) {
                print((Map) value, indent + "   ");
            }
        }
    }
}

输出效果(缩进清晰体现层级关系):

caso
   id
   unidadeDoCaso
      id
   etiqueta
   sigiloso
   idPecaSegredoJustica
   numeroAno
   numero
   competencia
      id
      ativo
      nome
   responsavel
      id
      dadosPessoais
         nome
   escrivao
      id
      dadosPessoais
         nome

⚠️ 注意事项:

  • 类型安全:Map 是运行时泛型擦除后的折中方案;若需编译期强约束,可封装为 NestedPathTree 自定义类,提供 add(String path) 和 getChildren(String... ancestors) 等方法。
  • 重复路径处理:当前 fill() 对重复路径(如两次 "caso.id")静默忽略,如需校验可添加 if (index == nodes.length - 1 && existing != null) 报警逻辑。
  • 空节点防御:生产环境建议在 split() 后过滤空字符串(Arrays.stream(nodes).filter(s -> !s.isBlank()).toArray(String[]::new)),避免 "a..b" 导致异常。
  • 性能考量:对于超大规模路径集(>10 万条),可考虑用

    Trie 或 ConcurrentHashMap 优化并发插入,但本方案已满足绝大多数元数据建模场景。

该模式本质是轻量级路径字典树(Path Trie)的 Map 实现,无需引入额外依赖,即可在纯 JDK 环境下完成灵活、可读、可扩展的嵌套结构构建。