完整代码
package com.pig4cloud.pigx.common.core.util.tree;
import java.util.*;
import java.util.function.function;
import java.util.stream.collectors;
/**
* 通用树结构构建工具类
*
* <p>重要说明:
* <ol>
* <li>所有节点必须具有唯一id</li>
* <li>父节点不存在时自动成为根节点</li>
* <li>节点排序依赖comparator实现</li>
* <li>支持循环依赖检测和错误路径提示</li>
* </ol>
*
* @param <t> 原始数据类型
* @param <k> 节点id类型(建议使用包装类型)
*/
public class treebuilder<t, k> {
private final function<t, k> idgetter;
private final function<t, k> parentidgetter;
private final childsetter<t> childsetter;
private final comparator<t> comparator;
/**
* 构造方法
*/
public treebuilder(function<t, k> idgetter,
function<t, k> parentidgetter,
childsetter<t> childsetter,
comparator<t> comparator) {
this.idgetter = objects.requirenonnull(idgetter, "id获取器不能为null");
this.parentidgetter = objects.requirenonnull(parentidgetter, "父id获取器不能为null");
this.childsetter = objects.requirenonnull(childsetter, "子节点设置器不能为null");
this.comparator = objects.requirenonnull(comparator, "排序比较器不能为null");
}
/**
* 构建完整树结构
*/
public list<t> buildtree(list<t> items) {
objects.requirenonnull(items, "节点列表不能为null");
if (items.isempty()) return collections.emptylist();
// 1. 构建数据索引
map<k, t> nodemap = createnodemap(items);
map<k, list<t>> parentchildrenmap = items.stream()
.collect(collectors.groupingby(
parentidgetter,
linkedhashmap::new, // 保持插入顺序
collectors.tolist()
));
// 2. 循环依赖检测
detectcyclicdependencies(items, nodemap);
// 3. 构建树结构
nodemap.foreach((nodeid, node) -> {
list<t> children = parentchildrenmap.getordefault(nodeid, collections.emptylist())
.stream()
.sorted(comparator)
.collect(collectors.tolist());
childsetter.setchildren(node, collections.unmodifiablelist(children));
});
// 4. 获取根节点(parentid为null或不存在于nodemap)
return items.stream()
.filter(item -> isrootnode(item, nodemap))
.sorted(comparator)
.collect(collectors.tolist());
}
/**
* 判断是否为根节点(抽离方法提升可读性)
*/
private boolean isrootnode(t item, map<k, t> nodemap) {
k parentid = parentidgetter.apply(item);
return parentid == null || !nodemap.containskey(parentid);
}
/**
* 构建搜索结果树
*/
public list<t> buildsearchtree(list<t> allitems, set<k> matchids) {
objects.requirenonnull(allitems, "节点列表不能为null");
objects.requirenonnull(matchids, "匹配id集合不能为null");
set<k> relatedids = findrelatedids(allitems, matchids);
list<t> relateditems = allitems.stream()
.filter(item -> relatedids.contains(idgetter.apply(item)))
.collect(collectors.tolist());
return buildtree(relateditems);
}
/**
* 创建节点id映射表(含重复检测)
*/
private map<k, t> createnodemap(list<t> items) {
map<k, t> map = new linkedhashmap<>(items.size());
for (t item : items) {
k id = idgetter.apply(item);
if (map.containskey(id)) {
throw new illegalargumentexception(string.format(
"发现重复节点id: %s (冲突对象1: %s, 冲突对象2: %s)",
id, map.get(id), item));
}
map.put(id, item);
}
return map;
}
/**
* 循环依赖检测核心逻辑
*/
private void detectcyclicdependencies(list<t> items, map<k, t> nodemap) {
set<k> verifiednodes = new hashset<>();
map<k, k> idtoparentmap = items.stream()
.collect(collectors.tomap(idgetter, parentidgetter));
for (t item : items) {
k currentid = idgetter.apply(item);
if (verifiednodes.contains(currentid)) continue;
set<k> path = new linkedhashset<>();
k tracingid = currentid;
while (tracingid != null) {
if (!path.add(tracingid)) {
throw new cyclicdependencyexception(buildcyclepath(path, tracingid));
}
// 短路已验证节点
if (verifiednodes.contains(tracingid)) break;
k parentid = idtoparentmap.get(tracingid);
if (parentid == null) break;
// 直接循环检测
if (parentid.equals(tracingid)) {
throw new cyclicdependencyexception("直接循环依赖: " + tracingid);
}
tracingid = parentid;
}
verifiednodes.addall(path);
}
}
/**
* 构造循环路径描述
*/
private string buildcyclepath(set<k> path, k duplicateid) {
list<k> pathlist = new arraylist<>(path);
int index = pathlist.indexof(duplicateid);
list<k> cycle = pathlist.sublist(index, pathlist.size());
return "检测到循环依赖链: " + cycle.stream()
.map(object::tostring)
.collect(collectors.joining(" → "));
}
/**
* 查找相关id集合(匹配节点+路径节点)
*/
private set<k> findrelatedids(list<t> allitems, set<k> matchids) {
map<k, k> idtoparentmap = allitems.stream()
.collect(collectors.tomap(idgetter, parentidgetter));
return matchids.stream()
.flatmap(id -> traceancestors(id, idtoparentmap).stream())
.collect(collectors.toset());
}
/**
* 追溯父节点链
*/
private set<k> traceancestors(k startid, map<k, k> idtoparentmap) {
set<k> ancestors = new linkedhashset<>();
k currentid = startid;
while (currentid != null && ancestors.add(currentid)) {
currentid = idtoparentmap.get(currentid);
}
return ancestors;
}
/**
* 自定义循环依赖异常
*/
public static class cyclicdependencyexception extends runtimeexception {
public cyclicdependencyexception(string message) {
super(message);
}
}
/**
* 子节点设置接口
*/
@functionalinterface
public interface childsetter<t> {
void setchildren(t parent, list<t> children);
}
/* 快捷构造方法 */
public static <t, k> treebuilder<t, k> create(
function<t, k> idgetter,
function<t, k> parentidgetter,
childsetter<t> childsetter,
comparator<t> comparator) {
return new treebuilder<>(idgetter, parentidgetter, childsetter, comparator);
}
public static <t, k extends comparable<? super k>> treebuilder<t, k> createwithnaturalorder(
function<t, k> idgetter,
function<t, k> parentidgetter,
childsetter<t> childsetter) {
return new treebuilder<>(
idgetter,
parentidgetter,
childsetter,
comparator.comparing(idgetter, comparator.nullslast(comparator.naturalorder()))
);
}
}一、设计思想与核心功能
本工具类采用泛型设计,可处理任意类型的节点数据,具备以下核心能力:
- 多类型支持:通过泛型参数t(数据类型)和k(id类型),支持各种业务场景
- 自动化构建:自动识别根节点、建立父子关系
- 安全防护:内置循环依赖检测、重复id校验
- 灵活扩展:支持自定义排序规则、子节点设置方式
- 高效查询:提供子树构建功能,适用于搜索场景
二、核心实现原理
1. 数据结构准备阶段
map<k, t> nodemap = createnodemap(items);
map<k, list<t>> parentchildrenmap = items.stream()
.collect(collectors.groupingby(...));- 节点映射表:通过id快速定位节点,验证id唯一性
- 父子关系映射:预先生成父节点→子节点列表的关系字典
2. 循环依赖检测算法
采用路径追踪法,时间复杂度o(n):
set<k> path = new linkedhashset<>();
while (tracingid != null) {
if (!path.add(tracingid)) {
throw new cyclicdependencyexception(...);
}
// 追溯父节点链
}
可检测两种异常情况:
- 直接循环:父节点指向自身
- 间接循环:a→b→c→a型循环链
3. 树形结构构建
采用两阶段构建模式:
- 初始化所有节点的子节点列表
- 筛选根节点(父id不存在或对应节点缺失)
4. 搜索子树生成
通过id回溯算法构建有效路径:
set<k> traceancestors(k startid) {
// 向上追溯所有祖先节点
}
确保搜索结果的完整树形结构
三、关键代码详解
1. 节点排序实现
childsetter.setchildren(node,
children.stream()
.sorted(comparator)
.collect(collectors.tolist())
);
支持两种排序方式:
- 自然排序(createwithnaturalorder)
- 自定义比较器(推荐业务相关排序)
2. 异常处理机制
自定义异常类型增强可读性:
public class cyclicdependencyexception extends runtimeexception {
// 携带具体循环路径信息
}
提供明确的错误定位信息:
检测到循环依赖链: 1001 → 1002 → 1003 → 1001
3. 函数式接口应用
@functionalinterface
public interface childsetter<t> {
void setchildren(t parent, list<t> children);
}
使用时可通过lambda表达式实现:
treebuilder<department, long> builder =
new treebuilder<>(..., (parent, children) -> parent.setchilddepts(children));
四、使用示例
基础用法
list<menu> menus = getfromdb();
treebuilder<menu, integer> builder = treebuilder.create(
menu::getid,
menu::getparentid,
(parent, children) -> parent.setchildren(children),
comparator.comparing(menu::getsortorder)
);
list<menu> tree = builder.buildtree(menus);
搜索场景应用
set<integer> matchids = searchservice.findids("关键");
list<menu> resulttree = builder.buildsearchtree(allmenus, matchids);
五、注意事项
- id规范:
- 必须实现有效的hashcode()和equals()
- 推荐使用包装类型(避免long与long的匹配问题)
- 对象状态:
- 原始数据对象应支持子节点集合设置
- 建议使用不可变集合防止意外修改
- 特殊场景:
- 空集合处理返回emptylist()
- 允许游离节点(父节点不存在时成为根节点)
- 性能考量:
- 万级数据量建议分批处理
- 频繁构建时可缓存nodemap
以上就是使用java实现通用树形结构构建工具类的详细内容,更多关于java构建树形结构的资料请关注代码网其它相关文章!
发表评论