Java通用树结构数据管理

Posted 阿拉伯1999

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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<T extends ITreeNodeData> implements Serializable {
	private static final long serialVersionUID = 1L;

	//节点数据对象
	private T nodeData;
	
	//父节点对象
	private TreeNode<T> parent;
	
	//子节点列表
	private List<TreeNode<T>> children = new ArrayList<TreeNode<T>>();
    
	//是否包含在树中,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<T extends ITreeNodeData> implements Serializable {
	private static final long serialVersionUID = 1L;

	//节点数据
	private T nodeData;
	
	//父节点对象
	private TreeNode<T> parent;
	
	//子节点
	private List<TreeNode<T>> children = new ArrayList<TreeNode<T>>();
	
	//是否包含在树中,1表示包含,0表示不包含,此属性为附加属性,在完整树剪枝时使用
	private Integer isIncluded = 0;
	
	/**
	 * 
	 * @methodName	: addChildNode
	 * @description	: 添加子节点 
	 * @param childNode	: 子节点
	 *
	 */
	public void addChildNode(TreeNode<T> childNode) {
		childNode.setParent(this);
		children.add(childNode);
	}
	
	/**
	 * 
	 * @methodName		: removeChildNode
	 * @description		: 移除子节点,如果子节点在子节点列表中,则移除,否则无影响
	 * @param childNode	: 子节点
	 *
	 */
	public void removeChildNode(TreeNode<T> childNode) {
		children.remove(childNode);
	}
	
	/**
	 * 
	 * @methodName		: getPrevSibling
	 * @description		: 取得左邻节点
	 * @return			: 如果当前节点为第一个节点,则返回null,否则为前一个节点
	 *
	 */
	public TreeNode<T> getPrevSibling(){
		if (parent == null) {
			//如果为根节点,则返回null
			return null;
		}
		
		List<TreeNode<T>> siblingList = parent.getChildren();
		TreeNode<T> node = null;
		for (int i = 0; i < siblingList.size(); i++) {
			TreeNode<T> 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<T> getNextSibling(){
		if (parent == null) {
			//如果为根节点,则返回null
			return null;
		}
		
		List<TreeNode<T>> siblingList = parent.getChildren();
		TreeNode<T> node = null;
		for (int i = 0; i < siblingList.size(); i++) {
			TreeNode<T> 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<T> lookUpSubNode(int nodeId){
		TreeNode<T> node = null;

		//检查当前节点
		if (nodeData.getNodeId() == nodeId) {
			node = this;
			return node;
		}
		
		//遍历子节点
		for(TreeNode<T> item : children) {			
			node = item.lookUpSubNode(nodeId);
			if (node != null) {
				//如果节点非空,表示查找到了
				break;
			}
		}
		return node;
	}
	
	/**
	 * 
	 * @methodName		: lookUpSuperNode
	 * @description		: 在当前节点及上级节点中查找指定节点ID的节点
	 * @param nodeId	: 节点ID
	 * @return			: 如果找到,返回对应树节点,否则返回null
	 *
	 */
	public TreeNode<T> lookUpSuperNode(int nodeId){
		TreeNode<T> node = null;

		//检查当前节点
		if (nodeData.getNodeId() == nodeId) {
			node = this;
			return node;
		}
		
		//查找父节点
		if (parent != null) {
			node = parent.lookUpSuperNode(nodeId);
		}
		
		return node;
	}
			
	/**
	 * 
	 * @methodName		: clone
	 * @description		: 复制树节点,包括所有子节点
	 * @return			: 复制后的树节点
	 *
	 */
	@SuppressWarnings("unchecked")
	public TreeNode<T> clone(){
		//复制当前节点数据信息
		TreeNode<T> treeNode = new TreeNode<T>();
		//节点数据
		treeNode.setNodeData((T)this.nodeData.clone());
		//是否包含
		treeNode.setIsIncluded(this.isIncluded);
		//复制所有子节点
		for(TreeNode<T> item : this.children) {
			//复制子节点
			TreeNode<T> childNode = item.clone();
			//加入子节点列表中
			treeNode.addChildNode(childNode);
		}
		return treeNode;
	}
	
	/**
	 * 
	 * @methodName		: setChildrenIsIncluded
	 * @description		: 设置所有子节点的是否包含属性 
	 * @param isIncluded	: 节点是否包含
	 *
	 */
	public void setChildrenIsIncluded(Integer isIncluded) {
		//遍历所有子节点
		for(TreeNode<T> item : this.children) {
			item.setIsIncluded(isIncluded);
			//子节点的子节点
			for(TreeNode<T> subItem : item.getChildren()) {
				subItem.setChildrenIsIncluded(isIncluded);
			}
		}
	}
	
	/**
	 * 
	 * @methodName		: combineTreeNode
	 * @description		: 将结构完全相同的节点合并到本节点中,合并后的节点的isIncluded属性位|操作
	 * @param combineNode: 并入的节点
	 *
	 */
	public void combineTreeNode(TreeNode<T> combineNode) {
		//当前节点数据的isIncluded属性,使用位|操作
		this.setIsIncluded(this.getIsIncluded() | combineNode.getIsIncluded());
		//合并子节点
		for (int i = 0; i < children.size(); i++) {
			TreeNode<T> item = children.get(i);
			TreeNode<T> 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<T> item = children.get(i);
			if (item.getIsIncluded() == 0) {
				//不包含,需要移除
				children.remove(i);
			}else {
				//包含,当前节点不需要移除,处理其子节点列表
				item.arrange();
			}			
		}
	}
	
	/**
	 * 
	 * @methodName		: getNodeList
	 * @description		: 获取包括自身及所有子节点的列表
	 * @param nodeList	: 树节点列表,入口参数为null
	 * @return			: 树节点列表
	 *
	 */
	public List<TreeNode<T>> getNodeList(List<TreeNode<T>> nodeList){
		
		if (nodeList == null) {
			//如果入口节点,则参数为null,需要创建
			nodeList = new ArrayList<TreeNode<T>>();
		}
		
		//加入自身节点
		nodeList.add(this);
		
		//加入所有子节点
		for(int i = 0; i < children.size(); i++) {
			TreeNode<T> childNode = children.get(i);
			childNode.getNodeList(nodeList);
		}
		
		return nodeList;
	}
	
	/**
	 * 
	 * @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<T> item : children) {
			//输出子节点数据
			if (sChild.equals("")) {
				sChild = item.toString();
			}else {
				sChild += "," + item.toString();
			}
		}
		sRet += sChild;
		//结束列表
		sRet += "]";
		if (parent != null) {
			//输出节点结束符号
			sRet += "}";
		}
		
		return sRet;
	}	
}

TreeNode类提供下列接口方法:

  • addChildNode方法,添加一个子节点。
  • removeChildNode方法,删除一个子节点。
  • getPrevSibling方法,取得左邻节点。
  • getNextSibling方法,取得右邻节点。
  • lookUpSubNode方法,在当前节点及下级节点中查找指定节点ID的节点。
  • lookUpSuperNode方法,在当前节点及上级节点中查找指定节点ID的节点。
  • clone方法,复制当前树节点表示的树或子树。
  • setChildrenIsIncluded方法,设置全部下级节点的isIncluded属性值。
  • combineTreeNode方法,将结构完全相同的节点合并到本节点中,合并后的节点的isIncluded属性作位或运算。两棵树合并,用完全相同结构的树合并要比不同结构的树合并要方便很多,如多种角色组合的权限树,先用全树合并,然后再剪枝,会方便很多。
  • arrange方法,对树进行剪枝处理,即所有isIncluded为0的节点,都被移除。
  • getNodeList方法,将所有节点对象(包含自身节点及所有下级节点),输出到列表中,便于外部进行遍历访问。由于树的遍历,需要递归,外部不好访问。
  • 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<FunctionInfo> selectAll();
}

​ 注意,查询数据需要按parent_id,order_no排序,且数据中,func_id要比按parent_id的值大,从而可以实现树结构的正常加载。

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<FunctionInfo> 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<FunctionInfo> functionTree;
	
	/**	
	 * 
	 * @methodName		: loadData
	 * @description		: 加载数据库中数据 
	 * @return			: 成功返回true,否则返回false。
	 *
	 */
	@Override
	public boolean loadData() {
		
		try {
			//查询function_tree表,获取全部数据
			List<FunctionInfo> functionInfoList = functionTreeDao.selectAll();
			
			//创建根节点
			functionTree = new TreeNode<FunctionInfo>();
			functionTree.setParent(null);
			//创建空节点数据
			functionTree.setNodeData(new FunctionInfo());
			//约定根节点的节点ID为0
			functionTree.getNodeData().setFuncId(0);
			//根节点总是包含的
			functionTree.setIsIncluded(1);
			
			//将查询结果放入functionTree对象中,按照树的结构组织
			//当前节点
			TreeNode<FunctionInfo> treeNode = null;
			//前一个节点的父节点,考虑到同一个父节点下的子节点对象是连续出现的
			//记下前一个节点的父节点,可以减少节点搜索次数
			TreeNode<FunctionInfo> preNodeParent = null;
			for(FunctionInfo item : functionInfoList) {
				//生成树节点
				treeNode = new TreeNode<FunctionInfo>();
				//设置节点数据
				treeNode.setNodeData(item);
				
				//如果前一节点的父节点不为空
				if (preNodeParent != null) {
					//比较当前父节点ID与前一节点的父节点的节点ID
					if(preNodeParent.getNodeData().getNodeId() == item.getParentId()) {
						//如果相等,表示父节点没有变化,继续加入此父节点
						preNodeParent.addChildNode(treeNode);
					}else {
						//如果父节点ID不同,则表示为新的父节点,从根节点向下搜索新的父节点
						preNodeParent = functionTree.lookUpSubNode(item.getParentId());
						if (preNodeParent != null) {
							//如果找到父节点,则加入
							preNodeParent.addChildNode(treeNode);
						}else {
							//指定父节点ID不在已有树中,数据加载次序错误,或数据错误
							log.error("FunctionTreeServiceImpl.loadData error with func_id = " + item.getFuncId());
							return false;
						}
					}
				}else {
					//第一个子节点
					functionTree.addChildNode(treeNode);
					preNodeParent = functionTree;
				}				
			}
			
		}catch(Exception e) {
			log.error(e.getMessage());
			e.printStackTrace();
			return false;
		}
		
		return true;
	 }
		
	/**
	 * 
	 * @methodName		: getFunctionTree
	 * @description		: 获取整个功能树
	 * @return			: 完整的功能树
	 *
	 */
	@Override
	public TreeNode<FunctionInfo> getFunctionTree(){
		return functionTree;
	}
	
}

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<FunctionInfo> functionTree = functionTreeService.getFunctionTree();
			System.out.println(functionTree);
		}
	}
}

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

以上是关于Java通用树结构数据管理的主要内容,如果未能解决你的问题,请参考以下文章

树——通用树的层次遍历

具有通用获取/设置实现的类属性树数据结构的框架/库?

java数据结构与算法之判断是否是二叉搜索树

数据结构(12)_树的概念及通用树的实现

数据结构—前缀树Trie的实现原理以及Java代码的实现

[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段