Java通用树结构数据管理

1、前言

​ 树结构是一种较为常见的数据结构,如功能权限树、企业的组织结构图、行政区划结构图、家族谱、信令消息树等,都表现为树型数据结构。

​ 树结构数据的共性是树节点之间都有相互关系,对于一个节点对象,可以找到父节点、左邻节点、右邻节点、子节点列表。节点本身有其数据类型对象,不同类型的树,不同之处在于节点数据类型不同。

​ 下面针对树型数据,用Java语言给出一种通用树结构数据定义,并提供常规的树节点操作方法。

2、树节点类定义

2.1、基本概念

  • 树节点:即treeNode,树节点是树结构的基本元素,整棵树是由一些列树节点串接而成。每个树节点,其父节点、左邻节点、右邻节点,或者是唯一的,或者为空,(否则成网状结构了)。树节点还包含子节点列表及自身数据对象。
  • 根节点:即rootNode,一棵树的根节点是唯一的,根节点的父节点为空。常见的树型结构数据,看起来好像有一组根节点,如导航栏菜单、菜单栏,实际上那是根节点的一级子节点。为了便于数据库对树型数据的存储,根节点的节点ID规定为0。
  • 叶子节点:即leafNode,叶子节点为树的末梢,叶子节点不包含子节点。
  • 树:使用树节点对象来表示一棵树,由于树节点包含子节点,子节点又包含子子节点。因此一个树节点,就是一棵子树;如果树节点为根节点,则表示整棵树。
  • 父节点:树节点的父节点,当前树节点在父节点的子节点列表中。
  • 子节点:树节点的子节点,在当前节点的子节点列表中。
  • 上级节点:或称祖先节点,从根节点到当前节点的路径上,不含当前节点的所有节点,都是上级节点。
  • 下级节点:或称子孙节点,以当前节点为上级节点的所有节点,都是下级节点。
  • 左邻节点:或称左兄弟节点,或前一节点,与当前节点拥有相同的父节点,且在父节点的子节点列表中,在当前节点的前一个。
  • 右邻节点:或称右兄弟节点,或后一节点,与当前节点拥有相同的父节点,且在父节点的子节点列表中,在当前节点的后一个。
  • 节点数据:每个节点包含一个节点数据对象,不同种类的树节点,其差异就是节点数据对象类型的不同。

2.2、树节点类

​ 树节点类TreeNode,其定义如下:

package com.abc.questInvest.vo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import lombok.Data;

/**
 * @className   : TreeNode
 * @description : 树节点
 * @summary : 节点数据类型,必须实现ITreeNodeData接口类的接口
 *
 */
@Data
public class TreeNode implements Serializable {
    private static final long serialVersionUID = 1L;

    //节点数据对象
    private T nodeData;

    //父节点对象
    private TreeNode parent;

    //子节点列表
    private List> children = new ArrayList>();

    //是否包含在树中,1表示包含,0表示不包含,此属性为附加属性,在完整树剪枝时使用
    private Integer isIncluded = 0;
}

​ 树节点类TreeNode使用泛型T,来表示节点数据类型,规定T必需实现ITreeNodeData接口类,使用接口类而不是基类,是为了不限定节点数据的字段集,且没有多重继承的问题。另外TreeNode也需要实现Serializable接口类,提供节点数据的序列化方法。

​ TreeNode类提供下列属性字段:

  • nodeData字段,节点数据对象,数据类型为泛型T。使用泛型,来达到通用树节点的目的。
  • parent字段,父节点对象,类型仍然是TreeNode。如果父节点为null,表示这是根节点。
  • children字段,子节点列表,其成员仍是TreeNode类型。
  • isIncluded字段,当前节点是否包含在树中,有时候,即使某个节点在树中,通过此属性字段,仍然可以指示该节点是否需要被剪枝。

​ TreeNode类,提供了父节点对象和子节点列表属性字段,从而具有向上搜索和向下搜索的能力。

​ TreeNode类,没有使用左邻节点、右邻节点属性字段,是考虑到兄弟节点的搜索不是很频繁,不用左邻节点、右邻节点属性字段,可以减少节点关系维护的复杂度。同级节点搜索,可以遍历父节点的子节点列表来实现。

3、树节点数据接口类

树节点数据接口类,为ITreeNodeData,其规定了树节点数据对象类型,必需实现的接口方法。代码如下:

package com.abc.questInvest.vo;

/**
 * @className   : ITreeNodeData
 * @description : 树节点数据接口类
 *
 */
public interface ITreeNodeData extends Cloneable{
    //=============节点基本属性访问接口==============================
    //获取节点ID
    int getNodeId();

    //获取节点名称
    String getNodeName();

    //获取父节点ID
    int getParentId();

    //=============Cloneable类接口===================================
    //克隆
    public Object clone();
}

​ ITreeNodeData类,继承Cloneable,要求树节点数据对象类型必需实现克隆(clone)接口方法。目的是实现对象复制。

​ ITreeNodeData类定义了下列基本的接口方法:

  • getNodeId方法,获取节点ID。
  • getNodeName方法,获取节点名称。
  • getParentId方法,获取父节点ID。
  • clone方法,实现Cloneable接口类需要重载的方法。

4、完整的树节点类

​ 对树节点类TreeNode,进行完善,提供必要的接口。代码如下:

package com.abc.questInvest.vo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import lombok.Data;

/**
 * @className   : TreeNode
 * @description : 树节点
 * @summary : 节点数据类型,必须实现ITreeNodeData接口类的接口
 *
 */
@Data
public class TreeNode implements Serializable {
    private static final long serialVersionUID = 1L;

    //节点数据
    private T nodeData;

    //父节点对象
    private TreeNode parent;

    //子节点
    private List> children = new ArrayList>();

    //是否包含在树中,1表示包含,0表示不包含,此属性为附加属性,在完整树剪枝时使用
    private Integer isIncluded = 0;

    /**
     *
     * @methodName  : addChildNode
     * @description : 添加子节点
     * @param childNode : 子节点
     *
     */
    public void addChildNode(TreeNode childNode) {
        childNode.setParent(this);
        children.add(childNode);
    }

    /**
     *
     * @methodName      : removeChildNode
     * @description     : 移除子节点,如果子节点在子节点列表中,则移除,否则无影响
     * @param childNode : 子节点
     *
     */
    public void removeChildNode(TreeNode childNode) {
        children.remove(childNode);
    }

    /**
     *
     * @methodName  : clear
     * @description : 移除所有子节点
     *
     */
    public void clear() {
        children.clear();
    }

    /**
     *
     * @methodName      : getPrevSibling
     * @description     : 取得左邻节点
     * @return      : 如果当前节点为第一个节点,则返回null,否则为前一个节点
     *
     */
    public TreeNode getPrevSibling(){
        if (parent == null) {
            //如果为根节点,则返回null
            return null;
        }

        List> siblingList = parent.getChildren();
        TreeNode node = null;
        for (int i = 0; i < siblingList.size(); i++) {
            TreeNode item = siblingList.get(i);
            if (item == this) {
                //找到当前节点
                if (i > 0) {
                    //当前节点不是第一个子节点
                    //取得前一个节点
                    node = siblingList.get(i-1);
                }
                break;
            }
        }
        return node;
    }

    /**
     *
     * @methodName      : getNextSibling
     * @description     : 取得右邻节点
     * @return      : 如果当前节点为最后一个节点,则返回null,否则为后一个节点
     *
     */
    public TreeNode getNextSibling(){
        if (parent == null) {
            //如果为根节点,则返回null
            return null;
        }

        List> siblingList = parent.getChildren();
        TreeNode node = null;
        for (int i = 0; i < siblingList.size(); i++) {
            TreeNode item = siblingList.get(i);
            if (item == this) {
                //找到当前节点
                if (i < siblingList.size()-1) {
                    //当前节点不是最后一个子节点
                    //取得后一个节点
                    node = siblingList.get(i+1);
                }
                break;
            }
        }
        return node;
    }

    /**
     *
     * @methodName      : lookUpSubNode
     * @description     : 在当前节点及下级节点中查找指定节点ID的节点
     * @param nodeId    : 节点ID
     * @return      : 如果找到,返回对应树节点,否则返回null
     *
     */
    public TreeNode lookUpSubNode(int nodeId){
        TreeNode node = null;

        //检查当前节点
        if (nodeData.getNodeId() == nodeId) {
            node = this;
            return node;
        }

        //遍历子节点
        for(TreeNode item : children) {
            node = item.lookUpSubNode(nodeId);
            if (node != null) {
                //如果节点非空,表示查找到了
                break;
            }
        }
        return node;
    }

    /**
     *
     * @methodName      : lookUpSubNode
     * @description     : 在当前节点及下级节点中查找指定节点名称的节点
     * @param nodeName  : 节点名称
     * @return      : 如果找到,返回对应树节点,否则返回null
     *
     */
    public TreeNode lookUpSubNode(String nodeName){
        TreeNode node = null;

        //检查当前节点
        if (nodeData.getNodeName().equals(nodeName)) {
            node = this;
            return node;
        }

        //遍历子节点
        for(TreeNode item : children) {
            node = item.lookUpSubNode(nodeName);
            if (node != null) {
                //如果节点非空,表示查找到了
                break;
            }
        }
        return node;
    }

    /**
     *
     * @methodName      : lookUpSuperNode
     * @description     : 在当前节点及上级节点中查找指定节点ID的节点
     * @param nodeId    : 节点ID
     * @return      : 如果找到,返回对应树节点,否则返回null
     *
     */
    public TreeNode lookUpSuperNode(int nodeId){
        TreeNode node = null;

        //检查当前节点
        if (nodeData.getNodeId() == nodeId) {
            node = this;
            return node;
        }

        //查找父节点
        if (parent != null) {
            node = parent.lookUpSuperNode(nodeId);
        }

        return node;
    }

    /**
     *
     * @methodName      : lookUpSuperNode
     * @description     : 在当前节点及上级节点中查找指定节点名称的节点
     * @param nodeName  : 节点名称
     * @return      : 如果找到,返回对应树节点,否则返回null
     *
     */
    public TreeNode lookUpSuperNode(String nodeName){
        TreeNode node = null;

        //检查当前节点
        if (nodeData.getNodeName().equals(nodeName)) {
            node = this;
            return node;
        }

        //查找父节点
        if (parent != null) {
            node = parent.lookUpSuperNode(nodeName);
        }

        return node;
    }

    /**
     *
     * @methodName      : clone
     * @description     : 复制树节点,包括所有子节点
     * @return      : 复制后的树节点
     *
     */
    @SuppressWarnings("unchecked")
    public TreeNode clone(){
        //复制当前节点数据信息
        TreeNode treeNode = new TreeNode();
        //节点数据
        treeNode.setNodeData((T)this.nodeData.clone());
        //是否包含
        treeNode.setIsIncluded(this.isIncluded);
        //复制所有子节点
        for(TreeNode item : this.children) {
            //复制子节点
            TreeNode childNode = item.clone();
            //加入子节点列表中
            treeNode.addChildNode(childNode);
        }
        return treeNode;
    }

    /**
     *
     * @methodName      : setChildrenIsIncluded
     * @description     : 设置所有子节点的是否包含属性
     * @param isIncluded    : 节点是否包含
     *
     */
    public void setChildrenIsIncluded(Integer isIncluded) {
        //遍历所有子节点
        for(TreeNode item : this.children) {
            item.setIsIncluded(isIncluded);
            //子节点的子节点
            item.setChildrenIsIncluded(isIncluded);
        }
    }

    /**
     *
     * @methodName      : combineTreeNode
     * @description     : 将结构完全相同的节点合并到本节点中,合并后的节点的isIncluded属性位|操作
     * @param combineNode   : 并入的节点
     *
     */
    public void combineTreeNode(TreeNode combineNode) {
        //当前节点数据的isIncluded属性,使用位|操作
        this.setIsIncluded(this.getIsIncluded() | combineNode.getIsIncluded());
        //合并子节点
        for (int i = 0; i < children.size(); i++) {
            TreeNode item = children.get(i);
            TreeNode combineItem = combineNode.getChildren().get(i);
            //合并子节点
            item.combineTreeNode(combineItem);
        }
    }

    /**
     *
     * @methodName  : arrange
     * @description : 对树进行剪枝处理,即所有isIncluded为0的节点,都被移除
     *
     */
    public void arrange() {
        //遍历子节点列表,如果子节点的isIncluded为0,则剪枝
        //倒序遍历
        for (int i = children.size() -1; i >=0; i--) {
            TreeNode item = children.get(i);
            if (item.getIsIncluded() == 0) {
                //不包含,需要移除
                children.remove(i);
            }else {
                //包含,当前节点不需要移除,处理其子节点列表
                item.arrange();
            }
        }
    }

    /**
     *
     * @methodName      : getNodeList
     * @description     : 获取包括自身及所有子节点的列表
     * @param nodeList  : 树节点列表,入口参数为null
     * @return      : 树节点列表
     *
     */
    public List> getNodeList(List> nodeList){

        if (nodeList == null) {
            //如果入口节点,则参数为null,需要创建
            nodeList = new ArrayList>();
        }

        //加入自身节点
        nodeList.add(this);

        //加入所有子节点
        for(int i = 0; i < children.size(); i++) {
            TreeNode childNode = children.get(i);
            childNode.getNodeList(nodeList);
        }

        return nodeList;
    }

    /**
     *
     * @methodName      : loadData
     * @description     : 将T类型对象的列表加载到树中,调用之前应确保节点的数据对象已创建,
     *                      且节点ID设置为0
     * @param inputList : T类型对象的列表
     * @return      : 错误的T类型对象的列表
     *
     */
    public List loadData(List inputList){
        //错误的数据对象列表
        List errorList = new ArrayList();

        //建立节点ID与节点对象的映射表,表示节点加载过程当前已加载的节点集合
        Map> nodeMap = new HashMap>();

        //==================================================================
        //要考虑数据次序不一定保证父节点已先加载的情况

        //清除数据
        clear();
        //先加入根节点
        nodeMap.put(this.nodeData.getNodeId(), this);

        //父节点
        TreeNode parentNode = null;

        //遍历inputList,加载树
        for(T item : inputList) {
            Integer parentId = item.getParentId();

            if (nodeMap.containsKey(parentId)) {
                //如果父节点已加载,取得父节点对象
                parentNode = nodeMap.get(parentId);
                //加载树节点
                addTreeNode(parentNode,item,nodeMap);
            }else {
                //如果父节点未加载,则暂时作为游离的独立节点或子树
                //加载树节点
                addTreeNode(null,item,nodeMap);
            }
        }

        //处理游离的节点
        for(TreeNode node : nodeMap.values()) {
            if (node.getParent() == null && node.getNodeData().getNodeId() != 0) {
                //父节点为空,且非根节点
                //取得父节点ID
                Integer parentId = node.getNodeData().getParentId();
                if (nodeMap.containsKey(parentId)) {
                    //如果父节点存在,,取得父节点对象
                    parentNode = nodeMap.get(parentId);
                    //加入父节点中
                    parentNode.addChildNode(node);
                }else {
                    //parentId对应的节点不存在,说明数据配置错误
                    errorList.add(node.getNodeData());
                }
            }
        }
        return errorList;
    }

    /**
     *
     * @methodName      : addTreeNode
     * @description     : 加入树节点
     * @param parentNode    : 父节点
     * @param dataInfo  : 节点信息对象
     * @param nodeMap   : 节点ID与节点对象的映射表
     *
     */
    private void addTreeNode(TreeNode parentNode, T dataInfo,
            Map> nodeMap) {
        //生成树节点
        TreeNode treeNode = new TreeNode();
        //设置节点数据
        treeNode.setNodeData((T)dataInfo);
        if(parentNode != null) {
            //父节点非空,加入父节点中
            parentNode.addChildNode(treeNode);
        }

        //加入nodeMap中
        nodeMap.put(dataInfo.getNodeId(), treeNode);
    }

    /**
     *
     * @methodName      : toString
     * @description     : 重载toString方法
     * @return      : 返回序列化输出的字符串
     *
     */
    @Override
    public String toString() {
        String sRet = "";

        //根节点的数据部分不必输出
        if (parent != null) {
            //非根节点
            //输出节点开始符号
            sRet = "{";
            //输出isIncluded值,剪枝后的树,无需输出此字段
            //sRet += "\"isIncluded\":" + isIncluded + ",";
            //输出当前节点数据
            sRet += "\"nodeData\":" + nodeData.toString();
            //与前一个节点分隔
            sRet += ",";
            sRet += "\"children\":";
        }

        //输出子节点
        //子节点列表
        sRet += "[";
        String sChild = "";
        //遍历子节点
        for(TreeNode item : children) {
            //输出子节点数据
            if (sChild.equals("")) {
                sChild = item.toString();
            }else {
                sChild += "," + item.toString();
            }
        }
        sRet += sChild;
        //结束列表
        sRet += "]";
        if (parent != null) {
            //输出节点结束符号
            sRet += "}";
        }

        return sRet;
    }
}

TreeNode类提供下列接口方法:

  • addChildNode方法,添加一个子节点。
  • removeChildNode方法,删除一个子节点。
  • clear方法,移除所有子节点。
  • getPrevSibling方法,取得左邻节点。
  • getNextSibling方法,取得右邻节点。
  • lookUpSubNode(int)方法,在当前节点及下级节点中查找指定节点ID的节点。
  • lookUpSubNode(String)方法,在当前节点及下级节点中查找指定节点名称的节点。
  • lookUpSuperNode(int)方法,在当前节点及上级节点中查找指定节点ID的节点。
  • lookUpSuperNode(String)方法,在当前节点及上级节点中查找指定节点名称的节点。
  • clone方法,复制当前树节点表示的树或子树。
  • setChildrenIsIncluded方法,设置全部下级节点的isIncluded属性值。
  • combineTreeNode方法,将结构完全相同的节点合并到本节点中,合并后的节点的isIncluded属性作位或运算。两棵树合并,用完全相同结构的树合并要比不同结构的树合并要方便很多,如多种角色组合的权限树,先用全树合并,然后再剪枝,会方便很多。
  • arrange方法,对树进行剪枝处理,即所有isIncluded为0的节点,都被移除。
  • getNodeList方法,将所有节点对象(包含自身节点及所有下级节点),输出到列表中,便于外部进行遍历访问。由于树的遍历,需要递归,外部不好访问。
  • loadData方法,将T类型对象的列表数据加载到树中。此方法要求外部先设置根节点的节点ID为0,结果实现树的构建,并输出未正确配置的数据对象列表。
  • toString方法,实现Serializable接口类需要重载的方法,提供树结构数据的序列化输出。

5、树结构的节点数据类示例

​ 树结构的节点数据,以权限管理的功能权限树为例,实体类为FunctionInfo。代码如下:

package com.abc.questInvest.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Id;

import com.abc.questInvest.vo.ITreeNodeData;

import lombok.Data;

/**
 * @className   : FunctionInfo
 * @description : 功能节点信息
 *
 */
@Data
public class FunctionInfo implements Serializable,ITreeNodeData {
    private static final long serialVersionUID = 1L;

    //功能ID
    @Id
    @Column(name = "func_id")
    private Integer funcId;

    //功能名称
    @Column(name = "func_name")
    private String funcName;

    //父节点ID
    @Column(name = "parent_id")
    private Integer parentId;

    //功能所在层级
    @Column(name = "level")
    private Byte level;

    //显示顺序
    @Column(name = "order_no")
    private Integer orderNo;

    //访问接口url
    @Column(name = "url")
    private String url;

    //dom对象的id
    @Column(name = "dom_key")
    private String domKey;

    // ================ 接口重载 ======================

    //获取节点ID
    @Override
    public int getNodeId() {
        return funcId;
    }

    //获取节点名称
    @Override
    public String getNodeName() {
        return funcName;
    }

    //获取父节点ID
    @Override
    public int getParentId() {
        return parentId;
    }

    //对象克隆
    @Override
    public Object clone(){
        FunctionInfo obj = null;
        try{
            obj = (FunctionInfo)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return obj;
    }

    @Override
    public String toString() {
        return "{"
                + "\"funcId\":" + funcId + ","
                + "\"funcName\":\"" + funcName + "\","
                + "\"parentId\":" + parentId + ","
                + "\"level\":" + level + ","
                + "\"orderNo\":" + orderNo + ","
                + "\"url\":\"" + url + "\","
                + "\"domKey\":\"" + domKey + "\""
                + "}";
    }

}

FunctionInfo类对应数据库的功能树表function_tree,表结构如下:

DROP TABLE IF EXISTS function_tree;
CREATE TABLE function_tree
(
  func_id       INT(11)      NOT NULL DEFAULT 0 COMMENT '功能ID',
  func_name     VARCHAR(100) NOT NULL DEFAULT '' COMMENT '功能名称',
  parent_id     INT(11)      NOT NULL DEFAULT 0 COMMENT '父功能ID',
  level         TINYINT(4)   NOT NULL DEFAULT 0 COMMENT '功能所在层级',
  order_no      INT(11)      NOT NULL DEFAULT 0 COMMENT '显示顺序',
  url           VARCHAR(80) NOT NULL DEFAULT '' COMMENT '访问接口url',
  dom_key       VARCHAR(80) NOT NULL DEFAULT '' COMMENT 'dom对象的id',

  remark        VARCHAR(200) NOT NULL DEFAULT '' COMMENT '备注',

  -- 记录操作信息
  operator_name VARCHAR(80)  NOT NULL DEFAULT '' COMMENT '操作人账号',
  delete_flag   TINYINT(4)   NOT NULL DEFAULT 0 COMMENT '记录删除标记,1-已删除',
  create_time   DATETIME(3)  NOT NULL DEFAULT NOW(3) COMMENT '创建时间',
  update_time   DATETIME(3)           DEFAULT NULL ON UPDATE NOW(3) COMMENT '更新时间',
  PRIMARY KEY (func_id)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8 COMMENT ='功能表';

6、功能树数据服务

6.1、Dao类

​ Dao类为FunctionTreeDao。代码如下:

package com.abc.questInvest.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import com.abc.questInvest.entity.FunctionInfo;

/**
 * @className   : FunctionTreeDao
 * @description : function_tree表数据访问类
 *
 */
@Mapper
public interface FunctionTreeDao {

    //查询所有功能树表记录,按parent_id,order_no排序
    @Select("SELECT func_id,func_name,parent_id,level,order_no,url,dom_key"
            + " FROM function_tree ORDER BY parent_id,order_no")
    List selectAll();
}

​ 注意,查询数据需要按parent_id,order_no排序,有助于提高加载速度。

6.2、Service类

​ Service类为FunctionTreeService。代码如下:

package com.abc.questInvest.service;

import com.abc.questInvest.entity.FunctionInfo;
import com.abc.questInvest.vo.TreeNode;

/**
 * @className   : FunctionTreeService
 * @description : 功能树服务
 *
 */
public interface FunctionTreeService {

    /**
     *
     * @methodName      : loadData
     * @description     : 加载数据库中数据
     * @return      : 成功返回true,否则返回false。
     *
     */
    public boolean loadData();

    /**
     *
     * @methodName      : getFunctionTree
     * @description     : 获取整个功能树
     * @return      : 完整的功能树
     *
     */
    public TreeNode getFunctionTree();
}

6.3、ServiceImpl类

​ Service实现类为FunctionTreeServiceImpl。代码如下:

package com.abc.questInvest.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.abc.questInvest.dao.FunctionTreeDao;
import com.abc.questInvest.entity.FunctionInfo;
import com.abc.questInvest.service.FunctionTreeService;
import com.abc.questInvest.vo.TreeNode;

import lombok.extern.slf4j.Slf4j;

/**
 * @className   : FunctionTreeServiceImpl
 * @description : FunctionTreeService实现类
 *
 */
@Slf4j
@Service
public class FunctionTreeServiceImpl implements FunctionTreeService {

    //function_tree表数据访问对象
    @Autowired
    private FunctionTreeDao functionTreeDao;

    //功能树对象
    private TreeNode functionTree = new TreeNode();

    /**
     *
     * @methodName      : loadData
     * @description     : 加载数据库中数据
     * @return      : 成功返回true,否则返回false。
     *
     */
    @Override
    public boolean loadData() {

        try {
            //查询function_tree表,获取全部数据
            List functionInfoList = functionTreeDao.selectAll();

            //加锁保护,防止脏读
            synchronized(functionTree) {
                //设置根节点
                setRootNode(functionTree);
                List errorList = functionTree.loadData(functionInfoList);
                if (errorList.size() > 0) {
                    //有错误信息
                    //写日志
                    for(FunctionInfo item : errorList) {
                        log.error("FunctionTree error with item : " + item.toString());
                    }
                    //此时,functionTree是剔除了异常数据的功能树
                    //返回true或false,视业务需求而定
                    return false;
                }
            }

        }catch(Exception e) {
            log.error(e.getMessage());
            e.printStackTrace();
            return false;
        }

        return true;
     }

    /**
     *
     * @methodName      : getFunctionTree
     * @description     : 获取整个功能树
     * @return      : 完整的功能树
     *
     */
    @Override
    public TreeNode getFunctionTree(){
        return functionTree;
    }

    /**
     *
     * @methodName      : setRootNode
     * @description     : 设置根节点
     * @param node      : 输入的功能树根节点
     *
     */
    private void setRootNode(TreeNode node) {
        node.setParent(null);
        //创建空节点数据
        node.setNodeData(new FunctionInfo());
        //约定根节点的节点ID为0
        node.getNodeData().setFuncId(0);
        node.getNodeData().setFuncName("root");
        //根节点总是包含的
        node.setIsIncluded(1);
    }
}

​### 6.4、单元测试

​ 对FunctionTreeService使用单元测试,观察效果。代码如下:

/**
 * @className   : QuestInvestApplicationTest
 * @description : 启动测试类
 *
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class QuestInvestApplicationTest {
    @Autowired
    ServletContext servletContext;

    @Autowired
    FunctionTreeService functionTreeService;

    @Test
    public void functionTreeServiceTest() {
        boolean bRet = false;
        bRet = functionTreeService.loadData();
        if (bRet) {
            TreeNode functionTree = functionTreeService.getFunctionTree();
            System.out.println(functionTree);
        }
    }
}

执行测试代码,可以看到输出的功能树数据,将之用网上的JSON查看器查看,可以看到下图的树型结构:

Java通用树结构数据管理

Original: https://www.cnblogs.com/alabo1999/p/14928380.html
Author: 阿拉伯1999
Title: Java通用树结构数据管理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/643118/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球