Tree Generation Tool Generic Interface TreeNode

1. Define a generic interface

// 实现接口需要传入两个参数,第一个T 为节点中的id,第二个R 为节点,TreeNode。
public interface TreeNode<T, R extends TreeNode<T, R>> {
    
    
    /**
     * 获取节点id
     *
     * @return 树节点id
     */
    T id();

    /**
     * 获取该节点的父节点id
     *
     * @return 父节点id
     */
    T parentId();

    /**
     * 是否是根节点
     *
     * @return true:根节点
     */
    boolean isRoot();

    /**
     * 设置节点的子节点列表
     *
     * @param children 子节点
     */
    void setChildren(List<R> children);

    /**
     * 获取所有子节点
     *
     * @return 子节点列表
     */
    List<R> getChildren();

    /**
     * 是否叶子节点
     *
     * @return 是否叶子节点
     */
    default boolean isLeaf() {
    
    
        return getChildren().size() == 0;
    }

    /**
     * 列表生成树结构
     *
     * @param nodes 要生成树状结构的集合
     * @param <T>   键类型
     * @param <R>   结点类型
     * @return 树结构
     */
    static <T, R extends TreeNode<T, R>> List<R> generateTree(List<R> nodes) {
    
    
        List<R> result = new ArrayList<>();
        // 从性能考虑。(nodes.size() / .75f) + 1 是HashMap总的空间大小
        Map<T, R> id2Node = new LinkedHashMap<>(Math.max((int) (nodes.size() / .75f) + 1, 16));
        nodes.forEach(e -> {
    
    
            id2Node.put(e.id(), e);
            e.setChildren(new ArrayList<>());
        });
        id2Node.forEach((id, node) -> {
    
    
            if (!node.isRoot()) {
    
     // 若非根节点
                R parent = id2Node.get(node.parentId()); // 拿到父节点
                if (parent != null) {
    
    
                    List<R> children = parent.getChildren(); // 拿到父节点的所有子节点
                    if (children == null) {
    
     // 子节点为null就初始化
                        children = new ArrayList<>();
                        parent.setChildren(children);
                    }
                    children.add(node); // 将当前节点加到父亲的子节点集合
                }
            } else {
    
     // 根节点
                result.add(node);
            }
        });
        return result; // 返回根节点
    }
}

2. Implement the generic interface

My requirement here is to return all administrative districts in the form of a tree. For example, province --> city --> district
So define the administrative tree structure:

@Data
@ApiModel(value = "行政区树结构VO")
// 这里必须实现 TreeNode<T, R> 其中R类型必须继承或实现了 TreeNode
public class DistrictTreeVO implements TreeNode<String, DistrictTreeVO> {
    
    

    @ApiModelProperty("行政区编码")
    private String districtCode;

    @ApiModelProperty("行政区名称")
    private String districtName;

    @ApiModelProperty("父行政区编码")
    private String parentDistrictCode;

    @ApiModelProperty("子级行政区")
    private List<DistrictTreeVO> children;

	// 实现泛型接口中未实现的方法。获取id,获取pid,判断是否是根节点
	// 泛型接口中已经实现的方法可以直接调用
    @Override
    public String id() {
    
    
        return this.getDistrictCode();
    }

    @Override
    public String parentId() {
    
    
        return this.getParentDistrictCode();
    }

    @Override
    public boolean isRoot() {
    
    
        return StringUtils.equals(this.getParentDistrictCode(), TradeXzq.MAX_PARENT_DISTRICT_CODE);
    }
}

3. Query test

insert image description here


DistrictTreeVO is R and String is T when the TreeNode.generateTree method is called. According to the implementation in the class DistrictTreeVO shall prevail.

4. To return to the paging tree structure

For example the following form:

insert image description here
The original idea is to recursively search for its child nodes for each first-level node after obtaining the first-level nodes according to the pagination, and set the value. But it's very inefficient, so consider the following approach!

The solution is: add a field path to the table. Used to represent the tree path identifier.
The path value of the first-level node is the id of the first-level node
The path value of the non-first-level node is the path value of the first-level node above it

(1) Query all first-level nodes according to the pagination conditions
(2) Query all nodes under these first-level nodes according to the Id of the queried first-level nodes (realized by adding a new field path, for the same tree All nodes of the path have the same path value)
(3) According to all the nodes obtained from these, just build a tree!

public PageResult<DeviceCategoryTreeRespVO> getDeviceCategoryPage(DeviceCategoryPageReqVO pageReqVO) {
    
    
	// 拿到一级节点
    PageResult<DeviceCategoryTreeRespVO> result = DeviceCategoryConvert.INSTANCE.convertTreePage(deviceCategoryMapper.selectTreePage(pageReqVO));
    // 通过 in 语句拿到所有一级节点下的节点(包括一级节点)只要path值为这几个一级节点的id之一,就说明是这几个一级节点下的点!
    List<DeviceCategoryTreeRespVO> childList = DeviceCategoryConvert.INSTANCE.convertTreeList(
            deviceCategoryMapper.selectList(Wrappers.lambdaQuery(DeviceCategoryDO.class)
                    .in(ObjectUtil.isNotNull(result.getList()), DeviceCategoryDO::getPath, result.getList().stream().map(po -> po.getId()).collect(Collectors.toList()))
                    .eq(DeviceCategoryDO::getShowStatus, "1"))
    );
    // 通过获取的这些节点 建树!
    result.setList(TreeNode.generateTree(childList));
    return result;
}

Guess you like

Origin blog.csdn.net/henulmh/article/details/128969960