Complete code
package com.; import .*; import ; import ; /** * General tree structure building tool class * * <p>Important Note: * <ol> * <li>All nodes must have unique IDs</li> * <li>The parent node automatically becomes the root node when it does not exist</li> * <li>Node sorting depends on comparator implementation</li> * <li>Support cyclic dependency detection and error path prompts</li> * </ol> * * @param <T> Primitive data type * @param <K> Node ID type (Packaging type is recommended) */ 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; /** * Constructing method */ public TreeBuilder(Function<T, K> idGetter, Function<T, K> parentIdGetter, ChildSetter<T> childSetter, Comparator<T> comparator) { = (idGetter, "ID getter cannot be null"); = (parentIdGetter, "The parent ID getter cannot be null"); = (childSetter, "The child node setter cannot be null"); = (comparator, "The sorting comparator cannot be null"); } /** * Build a complete tree structure */ public List<T> buildTree(List<T> items) { (items, "Node list cannot be null"); if (()) return (); // 1. Build data index Map<K, T> nodeMap = createNodeMap(items); Map<K, List<T>> parentChildrenMap = () .collect(( parentIdGetter, LinkedHashMap::new, // Keep insertion order () )); // 2. Circular dependency detection detectCyclicDependencies(items, nodeMap); // 3. Build a tree structure ((nodeId, node) -> { List<T> children = (nodeId, ()) .stream() .sorted(comparator) .collect(()); (node, (children)); }); // 4. Get the root node (parentId is null or does not exist in nodeMap) return () .filter(item -> isRootNode(item, nodeMap)) .sorted(comparator) .collect(()); } /** * Determine whether it is a root node (the extraction method improves readability) */ private boolean isRootNode(T item, Map<K, T> nodeMap) { K parentId = (item); return parentId == null || !(parentId); } /** * Build a search result tree */ public List<T> buildSearchTree(List<T> allItems, Set<K> matchIds) { (allItems, "Node list cannot be null"); (matchIds, "Match ID collection cannot be null"); Set<K> relatedIds = findRelatedIds(allItems, matchIds); List<T> relatedItems = () .filter(item -> ((item))) .collect(()); return buildTree(relatedItems); } /** * Create a node ID mapping table (including repeated detection) */ private Map<K, T> createNodeMap(List<T> items) { Map<K, T> map = new LinkedHashMap<>(()); for (T item : items) { K id = (item); if ((id)) { throw new IllegalArgumentException(( "Repeated nodes are foundID: %s (Conflict object1: %s, Conflict object2: %s)", id, (id), item)); } (id, item); } return map; } /** * Circular dependency detection core logic */ private void detectCyclicDependencies(List<T> items, Map<K, T> nodeMap) { Set<K> verifiedNodes = new HashSet<>(); Map<K, K> idToParentMap = () .collect((idGetter, parentIdGetter)); for (T item : items) { K currentId = (item); if ((currentId)) continue; Set<K> path = new LinkedHashSet<>(); K tracingId = currentId; while (tracingId != null) { if (!(tracingId)) { throw new CyclicDependencyException(buildCyclePath(path, tracingId)); } // Short circuit verified node if ((tracingId)) break; K parentId = (tracingId); if (parentId == null) break; // Direct cycle detection if ((tracingId)) { throw new CyclicDependencyException("Direct cyclic dependency: " + tracingId); } tracingId = parentId; } (path); } } /** * Construct loop path description */ private String buildCyclePath(Set<K> path, K duplicateId) { List<K> pathList = new ArrayList<>(path); int index = (duplicateId); List<K> cycle = (index, ()); return "Cyclic dependency chain detected: " + () .map(Object::toString) .collect((" → ")); } /** * Find the relevant ID collection (match node + path node) */ private Set<K> findRelatedIds(List<T> allItems, Set<K> matchIds) { Map<K, K> idToParentMap = () .collect((idGetter, parentIdGetter)); return () .flatMap(id -> traceAncestors(id, idToParentMap).stream()) .collect(()); } /** * Trace back to parent node chain */ private Set<K> traceAncestors(K startId, Map<K, K> idToParentMap) { Set<K> ancestors = new LinkedHashSet<>(); K currentId = startId; while (currentId != null && (currentId)) { currentId = (currentId); } return ancestors; } /** * Custom circular dependency exception */ public static class CyclicDependencyException extends RuntimeException { public CyclicDependencyException(String message) { super(message); } } /** * Children nodes set interface */ @FunctionalInterface public interface ChildSetter<T> { void setChildren(T parent, List<T> children); } /* Quick construction method */ 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, (idGetter, (())) ); } }
1. Design ideas and core functions
This tool class adopts a generic design and can process any type of node data, with the following core capabilities:
- Multi-type support: Supports various business scenarios through generic parameters T (data type) and K (ID type).
- Automated construction: Automatically identify the root node and establish a parent-child relationship
- Safety protection: Built-in cyclic dependency detection, repeated ID verification
- Flexible expansion: Supports custom sorting rules and child node settings
- Efficient query: Provides subtree construction function, suitable for search scenarios
2. Core implementation principle
1. Data structure preparation phase
Map<K, T> nodeMap = createNodeMap(items); Map<K, List<T>> parentChildrenMap = () .collect((...));
- Node Mapping Table: Quickly locate nodes through ID to verify ID uniqueness
- Father-son relationship mapping: Pre-master parent node → child node list relationship dictionary
2. Circular dependency detection algorithm
The path tracking method is used, and the time complexity O(n):
Set<K> path = new LinkedHashSet<>(); while (tracingId != null) { if (!(tracingId)) { throw new CyclicDependencyException(...); } // Trace back to parent node chain}
Two abnormal situations can be detected:
- Direct loop: The parent node points to itself
- Indirect loop: A→B→C→A type loop chain
3. Construction of tree structure
Adopt a two-stage construction model:
- Initialize the child node list of all nodes
- Filter the root node (the parent ID does not exist or the corresponding node is missing)
4. Search subtree generation
Build an effective path through the ID backtracking algorithm:
Set<K> traceAncestors(K startId) { // Trace upwards all ancestor nodes}
Ensure the complete tree structure of search results
3. Detailed explanation of the key code
1. Node sorting implementation
(node, () .sorted(comparator) .collect(()) );
Two sorting methods are supported:
- Natural sort (createWithNaturalOrder)
- Custom comparator (recommended business-related sorting)
2. Exception handling mechanism
Custom exception types enhance readability:
public class CyclicDependencyException extends RuntimeException { // Carry specific loop path information}
Provide clear error location information:
Circular dependency chain detected: 1001 → 1002 → 1003 → 1001
3. Functional interface application
@FunctionalInterface public interface ChildSetter<T> { void setChildren(T parent, List<T> children); }
When using it, it can be implemented through Lambda expressions:
TreeBuilder<Department, Long> builder = new TreeBuilder<>(..., (parent, children) -> (children));
IV. Use examples
Basic usage
List<Menu> menus = getFromDB(); TreeBuilder<Menu, Integer> builder = ( Menu::getId, Menu::getParentId, (parent, children) -> (children), (Menu::getSortOrder) ); List<Menu> tree = (menus);
Search scenario application
Set<Integer> matchIds = ("key"); List<Menu> resultTree = (allMenus, matchIds);
5. Things to note
-
ID specification:
- Efficient hashCode() and equals() must be implemented
- Recommended use of wrapper types (avoid long matching problems)
-
Object Status:
- The original data object should support child node collection settings
- It is recommended to use immutable collections to prevent accidental modifications
-
Special scenes:
- Empty collection processing returns emptyList()
- Allow free nodes (become the root node when the parent node does not exist)
-
Performance considerations:
- It is recommended to process data in batches in ten thousand
- NodeMap can be cached during frequent builds
The above is the detailed content of using Java to implement a general tree structure construction tool. For more information about Java to build a tree structure, please pay attention to my other related articles!