使用Java實現(xiàn)通用樹形結(jié)構(gòu)構(gòu)建工具類
完整代碼
package com.pig4cloud.pigx.common.core.util.tree;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 通用樹結(jié)構(gòu)構(gòu)建工具類
*
* <p>重要說明:
* <ol>
* <li>所有節(jié)點必須具有唯一ID</li>
* <li>父節(jié)點不存在時自動成為根節(jié)點</li>
* <li>節(jié)點排序依賴comparator實現(xiàn)</li>
* <li>支持循環(huán)依賴檢測和錯誤路徑提示</li>
* </ol>
*
* @param <T> 原始數(shù)據(jù)類型
* @param <K> 節(jié)點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;
/**
* 構(gòu)造方法
*/
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, "子節(jié)點設(shè)置器不能為null");
this.comparator = Objects.requireNonNull(comparator, "排序比較器不能為null");
}
/**
* 構(gòu)建完整樹結(jié)構(gòu)
*/
public List<T> buildTree(List<T> items) {
Objects.requireNonNull(items, "節(jié)點列表不能為null");
if (items.isEmpty()) return Collections.emptyList();
// 1. 構(gòu)建數(shù)據(jù)索引
Map<K, T> nodeMap = createNodeMap(items);
Map<K, List<T>> parentChildrenMap = items.stream()
.collect(Collectors.groupingBy(
parentIdGetter,
LinkedHashMap::new, // 保持插入順序
Collectors.toList()
));
// 2. 循環(huán)依賴檢測
detectCyclicDependencies(items, nodeMap);
// 3. 構(gòu)建樹結(jié)構(gòu)
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. 獲取根節(jié)點(parentId為null或不存在于nodeMap)
return items.stream()
.filter(item -> isRootNode(item, nodeMap))
.sorted(comparator)
.collect(Collectors.toList());
}
/**
* 判斷是否為根節(jié)點(抽離方法提升可讀性)
*/
private boolean isRootNode(T item, Map<K, T> nodeMap) {
K parentId = parentIdGetter.apply(item);
return parentId == null || !nodeMap.containsKey(parentId);
}
/**
* 構(gòu)建搜索結(jié)果樹
*/
public List<T> buildSearchTree(List<T> allItems, Set<K> matchIds) {
Objects.requireNonNull(allItems, "節(jié)點列表不能為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);
}
/**
* 創(chuàng)建節(jié)點ID映射表(含重復(fù)檢測)
*/
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(
"發(fā)現(xiàn)重復(fù)節(jié)點ID: %s (沖突對象1: %s, 沖突對象2: %s)",
id, map.get(id), item));
}
map.put(id, item);
}
return map;
}
/**
* 循環(huán)依賴檢測核心邏輯
*/
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));
}
// 短路已驗證節(jié)點
if (verifiedNodes.contains(tracingId)) break;
K parentId = idToParentMap.get(tracingId);
if (parentId == null) break;
// 直接循環(huán)檢測
if (parentId.equals(tracingId)) {
throw new CyclicDependencyException("直接循環(huán)依賴: " + tracingId);
}
tracingId = parentId;
}
verifiedNodes.addAll(path);
}
}
/**
* 構(gòu)造循環(huán)路徑描述
*/
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 "檢測到循環(huán)依賴鏈: " + cycle.stream()
.map(Object::toString)
.collect(Collectors.joining(" → "));
}
/**
* 查找相關(guān)ID集合(匹配節(jié)點+路徑節(jié)點)
*/
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());
}
/**
* 追溯父節(jié)點鏈
*/
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;
}
/**
* 自定義循環(huán)依賴異常
*/
public static class CyclicDependencyException extends RuntimeException {
public CyclicDependencyException(String message) {
super(message);
}
}
/**
* 子節(jié)點設(shè)置接口
*/
@FunctionalInterface
public interface ChildSetter<T> {
void setChildren(T parent, List<T> children);
}
/* 快捷構(gòu)造方法 */
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()))
);
}
}一、設(shè)計思想與核心功能
本工具類采用泛型設(shè)計,可處理任意類型的節(jié)點數(shù)據(jù),具備以下核心能力:
- 多類型支持:通過泛型參數(shù)T(數(shù)據(jù)類型)和K(ID類型),支持各種業(yè)務(wù)場景
- 自動化構(gòu)建:自動識別根節(jié)點、建立父子關(guān)系
- 安全防護:內(nèi)置循環(huán)依賴檢測、重復(fù)ID校驗
- 靈活擴展:支持自定義排序規(guī)則、子節(jié)點設(shè)置方式
- 高效查詢:提供子樹構(gòu)建功能,適用于搜索場景
二、核心實現(xiàn)原理
1. 數(shù)據(jù)結(jié)構(gòu)準(zhǔn)備階段
Map<K, T> nodeMap = createNodeMap(items);
Map<K, List<T>> parentChildrenMap = items.stream()
.collect(Collectors.groupingBy(...));- 節(jié)點映射表:通過ID快速定位節(jié)點,驗證ID唯一性
- 父子關(guān)系映射:預(yù)先生成父節(jié)點→子節(jié)點列表的關(guān)系字典
2. 循環(huán)依賴檢測算法
采用路徑追蹤法,時間復(fù)雜度O(n):
Set<K> path = new LinkedHashSet<>();
while (tracingId != null) {
if (!path.add(tracingId)) {
throw new CyclicDependencyException(...);
}
// 追溯父節(jié)點鏈
}
可檢測兩種異常情況:
- 直接循環(huán):父節(jié)點指向自身
- 間接循環(huán):A→B→C→A型循環(huán)鏈
3. 樹形結(jié)構(gòu)構(gòu)建
采用兩階段構(gòu)建模式:
- 初始化所有節(jié)點的子節(jié)點列表
- 篩選根節(jié)點(父ID不存在或?qū)?yīng)節(jié)點缺失)
4. 搜索子樹生成
通過ID回溯算法構(gòu)建有效路徑:
Set<K> traceAncestors(K startId) {
// 向上追溯所有祖先節(jié)點
}
確保搜索結(jié)果的完整樹形結(jié)構(gòu)
三、關(guān)鍵代碼詳解
1. 節(jié)點排序?qū)崿F(xiàn)
childSetter.setChildren(node,
children.stream()
.sorted(comparator)
.collect(Collectors.toList())
);
支持兩種排序方式:
- 自然排序(createWithNaturalOrder)
- 自定義比較器(推薦業(yè)務(wù)相關(guān)排序)
2. 異常處理機制
自定義異常類型增強可讀性:
public class CyclicDependencyException extends RuntimeException {
// 攜帶具體循環(huán)路徑信息
}
提供明確的錯誤定位信息:
檢測到循環(huán)依賴鏈: 1001 → 1002 → 1003 → 1001
3. 函數(shù)式接口應(yīng)用
@FunctionalInterface
public interface ChildSetter<T> {
void setChildren(T parent, List<T> children);
}
使用時可通過Lambda表達式實現(xiàn):
TreeBuilder<Department, Long> builder =
new TreeBuilder<>(..., (parent, children) -> parent.setChildDepts(children));
四、使用示例
基礎(chǔ)用法
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);
搜索場景應(yīng)用
Set<Integer> matchIds = searchService.findIds("關(guān)鍵");
List<Menu> resultTree = builder.buildSearchTree(allMenus, matchIds);
五、注意事項
- ID規(guī)范:
- 必須實現(xiàn)有效的hashCode()和equals()
- 推薦使用包裝類型(避免Long與long的匹配問題)
- 對象狀態(tài):
- 原始數(shù)據(jù)對象應(yīng)支持子節(jié)點集合設(shè)置
- 建議使用不可變集合防止意外修改
- 特殊場景:
- 空集合處理返回emptyList()
- 允許游離節(jié)點(父節(jié)點不存在時成為根節(jié)點)
- 性能考量:
- 萬級數(shù)據(jù)量建議分批處理
- 頻繁構(gòu)建時可緩存nodeMap
以上就是使用Java實現(xiàn)通用樹形結(jié)構(gòu)構(gòu)建工具類的詳細(xì)內(nèi)容,更多關(guān)于Java構(gòu)建樹形結(jié)構(gòu)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Maven配置文件settings.xml的實現(xiàn)
Maven是一個用于構(gòu)建和管理Java項目的強大工具,它依賴于設(shè)置文件來配置和管理其行為,其中最重要的之一便是settings.xml文件,本文主要介紹了Maven配置文件settings.xml的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-01-01
Java中struts2和spring MVC的區(qū)別_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了Java中struts2和spring MVC的區(qū)別,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-09-09
Java利用redis實現(xiàn)防止接口重復(fù)提交
本文主要為大家詳細(xì)介紹了Java如何利用redis實現(xiàn)防止接口重復(fù)提交,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-11-11
Spring Boot攔截器Interceptor與過濾器Filter詳細(xì)教程(示例詳解)
本文詳細(xì)介紹了SpringBoot中的攔截器(Interceptor)和過濾器(Filter),包括它們的定義、作用范圍、使用場景、實現(xiàn)步驟、執(zhí)行順序、常見問題及解決方案,感興趣的朋友跟隨小編一起看看吧2025-03-03
如何優(yōu)雅的拋出Spring Boot注解的異常詳解
這篇文章主要給大家介紹了關(guān)于如何優(yōu)雅的拋出Spring Boot注解的異常的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12
Spring Security 實現(xiàn)“記住我”功能及原理解析
這篇文章主要介紹了Spring Security 實現(xiàn)“記住我”功能及原理解析,需要的朋友可以參考下2020-05-05

