云计算百科
云计算领域专业知识百科平台

还在手写递归?这款Java树工具类一次搞定:构树、查父链、找兄弟,真香!

Java 树结构不会写?JTreeUtil 详细使用教程,构树、查父链、找兄弟节点一次讲透

Git 地址: https://github.com/paohaijiao/javelin

Maven 坐标:

<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>javelin-core</artifactId>
<version>2.5.0</version>
</dependency>

前言

在 Java 业务开发里,只要涉及组织架构、菜单权限、商品分类、区域层级,基本都绕不开树结构。

最常见的问题通常有这些:

  • 如何把平铺列表组装成树
  • 如何查某个节点
  • 如何拿某个节点的全部子孙
  • 如何拿当前节点的父链
  • 如何找兄弟节点
  • 如何把树重新拍平成列表

如果这些逻辑每个项目都自己手写一遍,成本很高,而且容易出错。

这篇文章从程序员实战角度,详细讲清楚 javelin-core 里的 JTreeUtil 怎么用,并配上详细示例。

一、JTreeUtil 是什么

JTreeUtil 是一个通用树结构工具类,用来处理父子层级关系数据。

它不只负责“构建树”,还提供了很多高频访问能力:

  • build:构建树
  • sortTree:递归排序
  • flatten:扁平化树
  • findNode:查找节点
  • getAllChildren:获取所有后代节点
  • getDirectChildren:获取直接子节点
  • getParents:获取父节点列表
  • getParentChain:获取从根到当前节点的完整路径
  • getSiblings:获取兄弟节点

这意味着它不仅能“造树”,还能“用树”。

二、先准备一个部门节点示例

1. 定义节点类

推荐做法是让节点类实现 JTreeNode。

import com.github.paohaijiao.tree.JTreeNode;
import java.util.List;

public class JDept implements JTreeNode<JDept, Long> {
private Long id;
private Long parentId;
private String name;
private List<JDept> children;

public JDept(Long id, Long parentId, String name) {
this.id = id;
this.parentId = parentId;
this.name = name;
}

@Override
public Long getId() {
return id;
}

@Override
public Long getParentId() {
return parentId;
}

public String getName() {
return name;
}

@Override
public List<JDept> getChildren() {
return children;
}

@Override
public void setChildren(List<JDept> children) {
this.children = children;
}
}

2. 准备测试数据

List<JDept> deptList = new ArrayList<>();
deptList.add(new JDept(1L, 0L, "总公司"));
deptList.add(new JDept(2L, 1L, "技术部"));
deptList.add(new JDept(3L, 1L, "市场部"));
deptList.add(new JDept(4L, 2L, "后端组"));
deptList.add(new JDept(5L, 2L, "前端组"));
deptList.add(new JDept(6L, 4L, "程序员小李"));
deptList.add(new JDept(7L, 4L, "程序员小王"));
deptList.add(new JDept(8L, 5L, "售后1"));
deptList.add(new JDept(9L, 5L, "售后2"));

三、JTreeUtil 的两种构树方式

1. 推荐方式:实现 JTreeNode 后直接构树

这是最省代码的方式。

List<JDept> tree = JTreeUtil.build(deptList, 0L);

这里的 0L 表示根节点的父 ID。

适合场景:

  • 组织架构树
  • 菜单树
  • 分类树
  • 评论回复树

2. 便捷访问方式:直接传方法引用

如果你的节点类不能改,或者不方便实现接口,也可以直接使用方法引用来构树。

List<DeptNode> tree = JTreeUtil.build(
deptList,
DeptNode::getId,
DeptNode::getParentId,
DeptNode::getChildren,
DeptNode::setChildren,
0L
);

这个方式的价值很高,因为它让你不改原类也能接入树工具。

这也是本文重点强调的“便捷访问方式”。

四、JTreeUtil 核心用法详解

1. 构建树

List<JDept> tree = JTreeUtil.build(deptList, 0L);

构建完成后,总公司 会作为根节点,技术部、市场部 挂在下面,后端组、前端组 挂到 技术部 下面。

2. 查找指定节点

比如要找 技术部:

JDept node = JTreeUtil.findNode(
tree,
2L,
JDept::getId,
JDept::getChildren
);

System.out.println(node.getName());

输出:

技术部

这个能力适合:

  • 根据部门 ID 查部门节点
  • 根据菜单 ID 查菜单节点
  • 根据分类 ID 查分类节点

3. 扁平化树结构

有些前端组件、导出逻辑或者递归处理,并不想拿树,而是要一个按深度优先展开的列表。

List<JDept> flattenList = JTreeUtil.flatten(tree, JDept::getChildren);

比如你可以继续拿它做展示:

for (JDept dept : flattenList) {
System.out.println(dept.getName());
}

4. 获取所有子节点

假设我们要拿 后端组 下的所有后代节点:

JDept backendGroup = JTreeUtil.findNode(
tree,
4L,
JDept::getId,
JDept::getChildren
);

List<JDept> allChildren = JTreeUtil.getAllChildren(
backendGroup,
JDept::getChildren
);

如果当前树里 后端组 下挂了 程序员小李、程序员小王,那么它们都会被拿出来。

适合场景:

  • 查询某部门下所有子部门
  • 查询某菜单下所有菜单项
  • 查询某分类下所有子分类

5. 获取直接子节点

如果你只想拿当前节点的下一层,而不是全部后代:

List<JDept> directChildren = JTreeUtil.getDirectChildren(
backendGroup,
JDept::getChildren
);

这个接口更适合:

  • 展示某层级下直接节点
  • 只查一级子菜单
  • 只查一级分类

6. 获取父节点列表

要获取某个节点的上级关系,需要先准备 nodeMap。

Map<Long, JDept> nodeMap = deptList.stream()
.collect(Collectors.toMap(JDept::getId, dept -> dept));

然后获取父节点列表:

List<JDept> parents = JTreeUtil.getParents(
backendGroup,
null,
JDept::getId,
JDept::getParentId,
nodeMap,
false
);

如果 backendGroup 是“后端组”,那么返回结果通常会是:

  • 技术部
  • 总公司

这个顺序是从近到远。

7. 获取完整父链

如果你想拿从根节点到当前节点的完整路径,可以使用 getParentChain。

List<JDept> chain = JTreeUtil.getParentChain(
backendGroup,
null,
JDept::getId,
JDept::getParentId,
nodeMap,
true
);

如果包含当前节点,结果顺序通常是:

  • 总公司
  • 技术部
  • 后端组

这在下面这些场景里非常常见:

  • 面包屑导航
  • 部门路径展示
  • 菜单路径回显
  • 分类路径定位

8. 获取兄弟节点

List<JDept> siblings = JTreeUtil.getSiblings(
backendGroup,
null,
JDept::getId,
JDept::getParentId,
JDept::getChildren,
nodeMap,
false
);

如果当前节点是 后端组,那么兄弟节点可能就是 前端组。

这个接口适合:

  • 查同级菜单
  • 查同级部门
  • 查同级分类

五、详细业务示例

示例 1:组织架构树返回给前端

public List<JDept> queryDeptTree() {
List<JDept> deptList = deptRepository.findAll();
return JTreeUtil.build(deptList, 0L);
}

这个接口非常适合前端树组件直接消费。

示例 2:根据部门 ID 返回完整路径

public List<String> queryDeptPath(Long deptId) {
List<JDept> deptList = deptRepository.findAll();
List<JDept> tree = JTreeUtil.build(deptList, 0L);
Map<Long, JDept> nodeMap = deptList.stream()
.collect(Collectors.toMap(JDept::getId, dept -> dept));

JDept node = JTreeUtil.findNode(tree, deptId, JDept::getId, JDept::getChildren);
List<JDept> chain = JTreeUtil.getParentChain(
node,
null,
JDept::getId,
JDept::getParentId,
nodeMap,
true
);

return chain.stream().map(JDept::getName).collect(Collectors.toList());
}

示例 3:查询某部门下的全部子部门

public List<JDept> queryAllChildren(Long deptId) {
List<JDept> deptList = deptRepository.findAll();
List<JDept> tree = JTreeUtil.build(deptList, 0L);
JDept node = JTreeUtil.findNode(tree, deptId, JDept::getId, JDept::getChildren);
return JTreeUtil.getAllChildren(node, JDept::getChildren);
}

示例 4:获取同级部门

public List<JDept> querySiblingDept(Long deptId) {
List<JDept> deptList = deptRepository.findAll();
List<JDept> tree = JTreeUtil.build(deptList, 0L);
Map<Long, JDept> nodeMap = deptList.stream()
.collect(Collectors.toMap(JDept::getId, dept -> dept));

JDept node = JTreeUtil.findNode(tree, deptId, JDept::getId, JDept::getChildren);
return JTreeUtil.getSiblings(
node,
null,
JDept::getId,
JDept::getParentId,
JDept::getChildren,
nodeMap,
false
);
}

六、排序用法

如果节点有排序字段,可以直接在构树时指定比较器。

List<JSimpleTreeNode<Integer>> tree = JTreeUtil.build(
nodes,
0,
TreeComparators.bySortOrderDesc()
);

也可以在树构建完成后再排序:

JTreeUtil.sortTree(tree);

或者指定自己的比较器:

JTreeUtil.sortTree(tree, (a, b) -> a.getId().compareTo(b.getId()));

这个能力特别适合:

  • 菜单排序
  • 分类排序
  • 部门顺序控制

七、JTreeUtil 使用建议

1. 优先让节点实现 JTreeNode

这样代码最短,构树最直接。

2. 不能改原类时就用方法引用

DeptNode::getId
DeptNode::getParentId
DeptNode::getChildren
DeptNode::setChildren

这是非常典型的便捷访问方式。

3. 父链和兄弟节点查询建议提前准备 nodeMap

这样查询更清晰,调用也更顺手。

4. 扁平化适合导出和递归处理

树不一定总是给前端组件,有时候拍平成列表反而更实用。

八、为什么这个工具对程序员很有用

很多树工具类只解决“从平铺列表组装成树”这一步,但真实业务里,光 build 还不够。

程序员真正高频会遇到的是:

  • 我要查某个节点
  • 我要拿某个节点的父路径
  • 我要拿全部子孙
  • 我要拿同级节点
  • 我要重新排序
  • 我要重新拍平成列表

JTreeUtil 把这些能力一次性补齐了,所以它更像一个“树结构工作台”,而不只是一个“构树方法”。

九、完整快速示例

List<JDept> tree = JTreeUtil.build(deptList, 0L);

JDept node = JTreeUtil.findNode(tree, 2L, JDept::getId, JDept::getChildren);

List<JDept> flattenList = JTreeUtil.flatten(tree, JDept::getChildren);

List<JDept> allChildren = JTreeUtil.getAllChildren(node, JDept::getChildren);

十、总结

如果你在 Java 项目里经常处理菜单树、部门树、分类树,那么 JTreeUtil 这类工具会非常省事。

它的价值不只是把列表转成树,而是把树后续常见的访问动作也统一收口了,包括:

  • 构树
  • 查节点
  • 查父链
  • 查兄弟节点
  • 查全部子孙
  • 扁平化
  • 排序
  • 方法引用式便捷访问

对于后端程序员来说,这种工具一旦稳定接入项目,后面处理层级结构的代码会轻很多。

关键词

JTreeUtil 使用教程、Java 树结构工具、Java 构建树形结构、Java 部门树、Java 菜单树、Java 获取父节点链、Java 获取兄弟节点、Java 扁平化树结构、Java TreeNode 工具类、javelin-core 教程

赞(0)
未经允许不得转载:网硕互联帮助中心 » 还在手写递归?这款Java树工具类一次搞定:构树、查父链、找兄弟,真香!
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!