SoFunction
Updated on 2025-04-13

Implementing a general tree structure building tool class using Java

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&lt;T, K&gt; {
    private final Function&lt;T, K&gt; idGetter;
    private final Function&lt;T, K&gt; parentIdGetter;
    private final ChildSetter&lt;T&gt; childSetter;
    private final Comparator&lt;T&gt; comparator;

    /**
      * Constructing method
      */
    public TreeBuilder(Function&lt;T, K&gt; idGetter,
                       Function&lt;T, K&gt; parentIdGetter,
                       ChildSetter&lt;T&gt; childSetter,
                       Comparator&lt;T&gt; 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&lt;T&gt; buildTree(List&lt;T&gt; items) {
        (items, "Node list cannot be null");
        if (()) return ();

        // 1. Build data index        Map&lt;K, T&gt; nodeMap = createNodeMap(items);
        Map&lt;K, List&lt;T&gt;&gt; parentChildrenMap = ()
                .collect((
                        parentIdGetter,
                        LinkedHashMap::new,  // Keep insertion order                        ()
                ));

        // 2. Circular dependency detection        detectCyclicDependencies(items, nodeMap);

        // 3. Build a tree structure        ((nodeId, node) -&gt; {
            List&lt;T&gt; 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 -&gt; isRootNode(item, nodeMap))
                .sorted(comparator)
                .collect(());

    }

    /**
      * Determine whether it is a root node (the extraction method improves readability)
      */
    private boolean isRootNode(T item, Map&lt;K, T&gt; nodeMap) {
        K parentId = (item);
        return parentId == null || !(parentId);
    }

    /**
      * Build a search result tree
      */
    public List&lt;T&gt; buildSearchTree(List&lt;T&gt; allItems, Set&lt;K&gt; matchIds) {
        (allItems, "Node list cannot be null");
        (matchIds, "Match ID collection cannot be null");

        Set&lt;K&gt; relatedIds = findRelatedIds(allItems, matchIds);
        List&lt;T&gt; relatedItems = ()
                .filter(item -&gt; ((item)))
                .collect(());

        return buildTree(relatedItems);
    }

    /**
      * Create a node ID mapping table (including repeated detection)
      */
    private Map&lt;K, T&gt; createNodeMap(List&lt;T&gt; items) {
        Map&lt;K, T&gt; map = new LinkedHashMap&lt;&gt;(());
        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&lt;T&gt; items, Map&lt;K, T&gt; nodeMap) {
        Set&lt;K&gt; verifiedNodes = new HashSet&lt;&gt;();
        Map&lt;K, K&gt; idToParentMap = ()
                .collect((idGetter, parentIdGetter));

        for (T item : items) {
            K currentId = (item);
            if ((currentId)) continue;

            Set&lt;K&gt; path = new LinkedHashSet&lt;&gt;();
            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&lt;K&gt; path, K duplicateId) {
        List&lt;K&gt; pathList = new ArrayList&lt;&gt;(path);
        int index = (duplicateId);
        List&lt;K&gt; cycle = (index, ());
        return "Cyclic dependency chain detected: " + ()
                .map(Object::toString)
                .collect((" → "));
    }

    /**
      * Find the relevant ID collection (match node + path node)
      */
    private Set&lt;K&gt; findRelatedIds(List&lt;T&gt; allItems, Set&lt;K&gt; matchIds) {
        Map&lt;K, K&gt; idToParentMap = ()
                .collect((idGetter, parentIdGetter));

        return ()
                .flatMap(id -&gt; traceAncestors(id, idToParentMap).stream())
                .collect(());
    }

    /**
      * Trace back to parent node chain
      */
    private Set&lt;K&gt; traceAncestors(K startId, Map&lt;K, K&gt; idToParentMap) {
        Set&lt;K&gt; ancestors = new LinkedHashSet&lt;&gt;();
        K currentId = startId;

        while (currentId != null &amp;&amp; (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&lt;T&gt; {
        void setChildren(T parent, List&lt;T&gt; children);
    }

    /* Quick construction method */

    public static &lt;T, K&gt; TreeBuilder&lt;T, K&gt; create(
            Function&lt;T, K&gt; idGetter,
            Function&lt;T, K&gt; parentIdGetter,
            ChildSetter&lt;T&gt; childSetter,
            Comparator&lt;T&gt; comparator) {
        return new TreeBuilder&lt;&gt;(idGetter, parentIdGetter, childSetter, comparator);
    }

    public static &lt;T, K extends Comparable&lt;? super K&gt;&gt; TreeBuilder&lt;T, K&gt; createWithNaturalOrder(
            Function&lt;T, K&gt; idGetter,
            Function&lt;T, K&gt; parentIdGetter,
            ChildSetter&lt;T&gt; childSetter) {
        return new TreeBuilder&lt;&gt;(
                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&lt;K&gt; path = new LinkedHashSet&lt;&gt;();
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&lt;K&gt; 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&lt;Integer&gt; matchIds = ("key");
List&lt;Menu&gt; 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!