java实现父子多级关联节点的最佳实践

本文介绍了在Java中处理父子节点关系,尤其是在菜单表设计中,通过单层for循环和Map集合实现时间复杂度更低的父子节点匹配。作者还提供了后端持久层和前端渲染的代码片段,以及对优化数据结构和算法在编码效率中的重要性的讨论。
摘要由CSDN通过智能技术生成

概要

父子节点匹配的的所有节点通常会设计存储在同一张数据表中,通过自连接操作找对孩子节点的父节点,那么这种操作在java中如何处理呢?
一种方式是使用多重循环匹配父节点的孩子节点,另一种方式是使用递归来对当前节点的孩子节点进行匹配,但是个人认为都不算是最佳的父子节点匹配处理方式。这里推荐一种时间复杂度更低的方式来对此场景进行处理,这里会使用使用单层for循环+Map集合进行遍历和匹配

表设计

以菜单表为例:这里想组装成三级菜单,id:菜单id,pid:父菜单id,通过pid与id的匹配来关联菜单层级,0代表顶层菜单
在这里插入图片描述

数据返回效果如下

[
        {
            "value": 1,
            "label": "主页",
            "pid": 0,
            "children": [
                {
                    "value": 2,
                    "label": "菜单1",
                    "pid": 1,
                    "children": [
                        {
                            "value": 4,
                            "label": "子项1",
                            "pid": 2
                        },
                        {
                            "value": 5,
                            "label": "子项2",
                            "pid": 2
                        }
                    ]
                },
                {
                    "value": 3,
                    "label": "菜单2",
                    "pid": 1,
                    "children": [
                        {
                            "value": 6,
                            "label": "子项3",
                            "pid": 3
                        },
                        {
                            "value": 7,
                            "label": "子项4",
                            "pid": 3
                        }
                    ]
                }
            ]
        }
    ]

关键代码片段实现

后端代码

//持久层对象
public class Menu {

    private Long id;

    private String name;

    private String icon;

    private Long pid;

    private List<Menu> menus;

	//set和get方法省略...
}
//返回给前端的数据对象(elementUI的级联选择组件根据以下字段进行渲染)
public class MenuDto {

    private Long value;

    private String label;

    private Long pid;

    private List<MenuDto> children;

	//set和get方法省略...
}

业务处理核心思路:遍历所有菜单,找出当前菜单是否存在父菜单,如果存在说明当前菜单是这个父菜单的子菜单,把子菜单添加到这个父菜单如果不存在则说明当前菜单就是一级菜单,添加到返回菜单列表中

//业务处理
@Override
public List<MenuDto> getMenuTree() {
    List<Menu> menuTree = menuDao.getMenuTree();
    Map<Long, MenuDto> menuDtoMap = menuTree.stream().map(menu -> {
        MenuDto menuDto = new MenuDto();
        menuDto.setValue(menu.getId());
        menuDto.setLabel(menu.getName());
        menuDto.setPid(menu.getPid());
        return menuDto; //将持久层对象转换成前端渲染对象
    }).collect(Collectors.toMap(MenuDto::getValue, menuDto -> menuDto)); //把list收集成map集合,key为id,value为menuDto对象

    List<MenuDto> parents = new ArrayList<>(); //定义菜单返回列表

    for (Map.Entry<Long, MenuDto> entry : menuDtoMap.entrySet()) {
        MenuDto menuDto = entry.getValue(); //获取当前菜单
        MenuDto parent = menuDtoMap.get(menuDto.getPid());//获取当前菜单的父菜单
        if (ObjectUtil.isNotNull(parent)) { //如果当前菜单的父菜单不为空说明此菜单不是顶层菜单,而是子菜单
            if (ObjectUtil.isNull(parent.getChildren())) { //当首次执行会给父菜单的子菜单列表进行初始化
                parent.setChildren(new ArrayList<>());
            }
            parent.getChildren().add(menuDto); //把当前菜单作为子菜单添加到它的父菜单的子菜单列表中
        } else { //如果找不到当前菜单的父菜单,那它自己就是顶层菜单添加到返回列表
            parents.add(menuDto); 
        }
    }
    return parents;
}

前端代码

<template>
  <div>
    <el-cascader :options="options"></el-cascader>
  </div>
</template>

<script>
import _axios from "../util/myaxios";
export default {
  data() {
    return {
        options: [
            // {value:100, label:'主页', children:[
            //     {value:101, label:'菜单1', children:[
            //         {value:105, label:'子项1'},
            //         {value:106, label:'子项2'}
            //     ]},
            //     {value:102, label:'菜单2', children:[
            //         {value:105, label:'子项3'},
            //         {value:106, label:'子项4'},
            //         {value:106, label:'子项5'}
            //     ]},
            //     {value:103, label:'菜单3', children:[
            //         {value:105, label:'子项6'},
            //         {value:106, label:'子项7'}
            //     ]},
            //     {value:106, label:'菜单4'}
            // ]}
        ]
    };
  },
  methods: {
    async query() {
      const resp = await _axios.get("/query/menu/tree");
      this.options = resp.data.data; //将返回的列表数据渲染给级联选择组件
    },
  },
  mounted() {
    this.query();
  }
};
</script>

界面展示

在这里插入图片描述

小结

数据组装往往涉及到循环遍历和匹配,有时候可以考虑使用更合适的数据结构或算法来提高业务处理效率,这些都是编码过程中的细节,博主以前最初使用过多重循环来处理过三级评论列表查询的场景,在三级商品菜单列表查询的场景下使用的是递归处理,而现在回顾起来并不是最优雅的处理方式,不过在实际开发中始终还是要牢牢结合业务场景进行代码分析和优化才有真正意义。

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
要返回父子关联表的树形结构,你可以使用递归算法来实现。以下是一个示例的 Java 代码: ```java import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class TreeStructure { public static class Node { private int id; private String name; private int parentId; private List<Node> children; public Node(int id, String name, int parentId) { this.id = id; this.name = name; this.parentId = parentId; this.children = new ArrayList<>(); } public int getId() { return id; } public String getName() { return name; } public int getParentId() { return parentId; } public List<Node> getChildren() { return children; } public void addChild(Node child) { children.add(child); } } public static List<Node> buildTree(List<Node> nodes) { Map<Integer, Node> nodeMap = new HashMap<>(); // 将节点放入哈希表中,以便根据 id 查找节点 for (Node node : nodes) { nodeMap.put(node.getId(), node); } List<Node> tree = new ArrayList<>(); // 根据 parentId 构建树结构 for (Node node : nodes) { int parentId = node.getParentId(); if (parentId == 0) { // 根节点 tree.add(node); } else { Node parent = nodeMap.get(parentId); if (parent != null) { parent.addChild(node); } } } return tree; } public static void main(String[] args) { // 示例数据 List<Node> nodes = new ArrayList<>(); nodes.add(new Node(1, "A", 0)); nodes.add(new Node(2, "B", 1)); nodes.add(new Node(3, "C", 1)); nodes.add(new Node(4, "D", 2)); nodes.add(new Node(5, "E", 4)); List<Node> tree = buildTree(nodes); // 遍历树结构 for (Node node : tree) { printTree(node, 0); } } private static void printTree(Node node, int level) { StringBuilder indent = new StringBuilder(); for (int i = 0; i < level; i++) { indent.append(" "); } System.out.println(indent.toString() + node.getName()); for (Node child : node.getChildren()) { printTree(child, level + 1); } } } ``` 这段代码中,我们定义了一个 `Node` 类来表示树的节点,包含 id、name、parentId 和 children 属性。然后,我们使用 `buildTree` 方法来构建树结构,该方法接受一个节点列表作为输入,并返回树的根节点列表。最后,我们通过递归遍历树结构,并打印出每个节点的名称来验证结果。 你可以根据实际需求修改代码,并根据需要进行扩展。希望能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白条君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值