树形结构是我们开发过程中经常遇到的一种数据结构
例如:权限树,菜单树,分类树……

数据表结构

其数据库设计大多如下:

create table sys_menu
(id   varchar(64) primary key not null,name varchar(64)             not null comment '菜单名称',pid  varchar(64)             not null default '0' comment '父id'
) comment '系统菜单表';

实体类


@Data
@Accessors(chain = true)
public class SysMenu {/*** id*/private String id;/*** 菜单名称*/private String name;/*** 父id*/private String pid;}

设计结构

根据数据库可以设计出如下的数据结构

  • data: 数据库任意一行数据
  • children:data节点下的所有数据集合

即:

/*** 树形结构模型类** @author zukxu* @since 2022-1-2-18:09:32*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TreeNode<T> {/*** 节点根数据*/private T rootNode;/*** 节点内容*/private List<TreeNode<T>> childrenNode = new ArrayList<>();

工具类-ConvertTree

这是一个将其他的数据转换为Tree的工具类

public class ConvertTree<T> {}

获取数据

一次性从数据库中获取全部的数据,将取出的数据放入一个list中

每一个节点都应该有获取其子节点的方法
在树形节点类中添加一个方法获取其子节点内容
通过rootNode的id对比相同的放入子节点集合中,不相同的从数据集合中删除,只要集合为空即遍历结束

 /*** 获取子节点** @param dataList 数据集合* @param idName   id字段名* @param pidName  pid字段名** @return 子节点集合*/public List<TreeNode<T>> childrenNode(List<T> dataList, String idName, String pidName) {ConvertTree<T> convertTree = new ConvertTree<>();String idValue = convertTree.getFieldValue(rootNode, idName);List<T> collect = dataList.stream().filter(t -> idValue.equals(convertTree.getFieldValue(t, pidName))).toList();dataList.removeAll(collect);collect.forEach(t -> {TreeNode<T> treeNode = new TreeNode<>();treeNode.setRootNode(t);childrenNode.add(treeNode);});return childrenNode;}

由于无法知道具体的类,也就不知道构建Tree的id字段和pid字段,所以我们可以采用反射的方式获取字段的值

 /*** 根据反射获取字段值** @param obj* @param fieldName** @return*/public String getFieldValue(T obj, String fieldName) {Class<?> cls = obj.getClass();//获取所有属性Field[] fields = cls.getFields();for(Field field : fields) {try {//打开私有访问,允许访问私有变量field.setAccessible(true);//获取属性if(field.getName().equals(fieldName)) {Object res = field.get(obj);if(ObjectUtil.isEmpty(res)) {return null;}return res.toString();}} catch(IllegalAccessException e) {e.printStackTrace();}}throw new RuntimeException("获取属性值错误");}

找出根节点

找出数据中的根节点,也就是没有pid的值
判断数据集合是否为空,取出集合中的第一个元素,并递归往上找,知道找不到父节点为止
将这个根节点放入我们的树结构中,并且通过children获取子节点数据

/*** 获取根节点** @param dataList* @param idName* @param pidName** @return*/public TreeNode<T> getRootNode(List<T> dataList, String idName, String pidName) {if(dataList.isEmpty()) {return null;}T node = dataList.get(0);T rootNode = getRootNode(dataList, idName, pidName, node);TreeNode<T> rootTreeNode = new TreeNode<>();dataList.remove(rootNode);rootTreeNode.setRootNode(rootNode);rootTreeNode.childrenNode(dataList, idName, pidName);return rootTreeNode;}/*** 递归遍历根节点** @param dataList* @param idName* @param pidName* @param node** @return*/private T getRootNode(List<T> dataList, String idName, String pidName, T node) {T fNode = null;String fieldValue = getFieldValue(node, pidName);for(T data : dataList) {if(getFieldValue(data, idName).equals(fieldValue)) {fNode = data;break;}}if(ObjectUtil.isEmpty(fNode)) {return node;} else {return getRootNode(dataList, idName, pidName, fNode);}}

获取树形数据结构

根据获取到的root节点,构建成一颗树形数据

    /*** 生成树结构** @param dataList* @param idName* @param pidName** @return*/public TreeNode<T> getTree(List<T> dataList, String idName, String pidName) {//获取树根TreeNode<T> rootNode = getRootNode(dataList, idName, pidName);//    遍历树节点List<TreeNode<T>> childrenNodeList = rootNode.getChildrenNode();forChildren(dataList, idName, pidName, childrenNodeList);//    返回树return rootNode;}/*** 递归遍历子节点** @param dataList* @param idName* @param pidName* @param childrenNodeList*/private void forChildren(List<T> dataList, String idName, String pidName, List<TreeNode<T>> childrenNodeList) {//遍历集合List<TreeNode<T>> needForList = new ArrayList<>();for(TreeNode<T> tTreeNode : childrenNodeList) {List<TreeNode<T>> treeNodes = tTreeNode.childrenNode(dataList, idName, pidName);needForList.addAll(treeNodes);}if(!needForList.isEmpty()) {forChildren(dataList, idName, pidName, needForList);}}

生成森林

这种方法只会生成一个根节点的树,
但是我们在实际使用过程中的树结构会生成多个根节点的树,我们可以依次生成多棵树然后添加到list中返回
或者:

    /*** 形成森林数据结构** @param dataList* @param idName* @param pidName** @return*/public List<TreeNode<T>> getForest(List<T> dataList, String idName, String pidName) {List<TreeNode<T>> forest = new ArrayList<>();while(!dataList.isEmpty()) {TreeNode<T> tree = getTree(dataList, idName, pidName);forest.add(tree);}return forest;}

工具类改进

我们之前的方法需要在代码中硬编码id对应的字段名和父id对应的字段名,这种硬编码的方式不适合我们的开发和后续的更新维护

注解方式

我们可以通过注解的方式获得对应的id字段名称和父id字段名称

 /*** 形成森林(使用注解)** @param dataList*/public List<TreeNode<T>> getForest(List<T> dataList) {//通过注解获取idName和pidNameString idName = null;String pidName = null;if(!dataList.isEmpty()) {//得到classClass<?> cls = dataList.get(0).getClass();//得到所有属性Field[] fields = cls.getDeclaredFields();for(Field field : fields) {TreeId treeId = field.getAnnotation(TreeId.class);if(treeId != null) {idName = field.getName();}TreePid treeFid = field.getAnnotation(TreePid.class);if(treeFid != null) {pidName = field.getName();}}}return getForest(dataList, idName, pidName);}

注解-TreeId

/*** 标识TreeId** @author zukxu* @since 2022/1/2 19:13:29*/
@Retention(RetentionPolicy.RUNTIME)
@Target(FIELD)
public @interface TreeId {}

注解-Pid

/*** 标识Pid** @author zukxu* @since 2022/1/2 19:13:53*/
@Retention(RetentionPolicy.RUNTIME)
@Target(FIELD)
public @interface TreePid {}

使用

测试类


@Data
@Accessors(chain = true)
public class SysMenu {/*** id*/@TreeIdprivate String id;/*** 菜单名称*/private String name;/*** 父id*/@TreePidprivate String pid;}

测试方法

@Testvoid testTreeNode() {Connection conn = null;Statement stat = null;ResultSet res = null;try {Class.forName(driverClassName);conn = getConnection();String sql = "select * from sys_menu";stat = conn.createStatement();res = stat.executeQuery(sql);List<SysMenu> menuList = new ArrayList<>();while(res.next()) {String id = res.getString("id");String name = res.getString("name");String pid = res.getString("pid");menuList.add(new SysMenu().setId(id).setName(name).setPid(pid));}buildTree(menuList);} catch(SQLException | ClassNotFoundException e) {e.printStackTrace();} finally {close(res, stat, conn);}}private void buildTree(List<SysMenu> menuList) {ConvertTree<SysMenu> convertTree = new ConvertTree<>();//硬编码List<TreeNode<SysMenu>> forest = convertTree.getForest(menuList, "id", "pid");System.out.println(JSON.toJSONString(forest));//注解方式List<TreeNode<SysMenu>> forest1 = convertTree.getForest(menuList);System.out.println(JSON.toJSONString(forest1));}

Java树形结构设计与开发相关推荐

  1. openjweb1.8 java web应用快速开发平台产品白皮书

    因图片较多,需要图片请到资源中下载,不需要资源分.           OpenJWeb(1.8) Java Web应用快速开发平台   产品白皮书               编者:OpenJWeb ...

  2. java ee web高级,Java EE Web高级开发案例

    核心提示:Java EE Web高级开发案例 内容简介:<Java EE Web高级开发案例>充分体现了高等职业教育的特点,突出了理论和实践的紧密结合,以充分掌握基本技术技能和必要的基本知 ...

  3. OpenJWeb(1.6) Java Web应用快速开发平台技术白皮书

    OpenJWeb中国开源组织(http://blog.csdn.net/baozhengw) 苏州创智科技有限公司(http://www.cmissoft.com) QQ:29803446 Msn:b ...

  4. MPSDK4J 是JAVA微信公平台开发SDK,没有复杂的功能,一切源于微信API,愿你会喜欢使用。-- 题记

    MPSDK4J 是JAVA微信公平台开发SDK,没有复杂的功能,一切源于微信API,愿你会喜欢使用.-- 题记 1.介绍 MPSDK4J,非常直观的阐述了此项目的意义所在.没错,它就是JAVA语言环境 ...

  5. Java 8 失宠!开发人员向 Java 11 转移...

    以下文章来源方志朋的博客,回复"666"获面试宝典 作者:白开水 来源:OSC开源社区(ID:oschina2013) 此前的 Java 社区报告曾指出,Java 8 仍是开发人员 ...

  6. 《Java EE企业级应用开发》,《分布式爬虫》等书包邮送50本!企业开发利器!...

    来给大家送一波福利,这次联系了10个好友一起给各位送书,每个号送 5 本,一共 50本,还包邮哦. 感谢传智播客对本次活动的赞助.   金主介绍:传智播客是国内数一数二的IT培训机构,现在关注传智播客 ...

  7. 慕课网_《Java微信公众号开发进阶》学习总结

    时间:2017年08月12日星期六 说明:本文部分内容均来自慕课网.@慕课网:http://www.imooc.com 教学源码:http://img.mukewang.com/down/... 学习 ...

  8. java微信公众号开发token验证失败的问题及解决办法

    java微信公众号开发token验证失败的问题及解决办法 参考文章: (1)java微信公众号开发token验证失败的问题及解决办法 (2)https://www.cnblogs.com/beardu ...

  9. 是否可以将Java 8用于Android开发?

    本文翻译自:Is it possible to use Java 8 for Android development? Searching the web, it is not clear if Ja ...

最新文章

  1. linux下clone一直运行,如何在Linux上使用clone()创建真正的线程?
  2. win10x64下的redis安装与使用
  3. EasyUI DateTimeBox设置默认时间的注意点
  4. react打包后图片丢失_如何快速构建React组件库
  5. 餐饮创业想赚钱,这5个思维方式少不了
  6. pythoninit_Python __init__.py文件的作用
  7. ajax跨域,json,jsonp
  8. 《Unity3d-控制枪口的朝向代码》
  9. EditorUtility.SetDirty 设置已改变
  10. 单片机最小系统着实让人着迷
  11. 【小程序】小程序安卓,ios,ipad兼容问题
  12. 南非SABS EMC CoC简介
  13. 如何搭建自己的微信公众号?
  14. word的奇葩功能--隐藏文字
  15. 计算机上面的按键作用,鼠标侧键有什么用 鼠标上各按键的功能是什么
  16. 保留核心,刺激进步 ——读《马克思传》有感
  17. mysql sql并列排名_教你用SQL实现统计排名
  18. eclipse的工作空间如何复制
  19. Selenium Webdriver重新使用已打开的浏览器实例
  20. 素材火官网后台模板下载

热门文章

  1. RJ45以太网接口EMC设计方案
  2. The server time zone value ‘й‘ is unrecognized or represents more than one time zone
  3. 关系数据模型和SQL基础
  4. Unable to resolve dependency for :app@debug/compileClasspath': Could not resolve com.***问题解决
  5. activeroot翻译,Active翻译
  6. 《第6章-GCN的性质》学习笔记
  7. 高性能电工电子电拖及自动化技术实训与考核装置
  8. 国际标准分类法ICS
  9. 2019,我的工作寻找之路
  10. C#毕业设计——基于C#+asp.net+FTP的FTP客户端设计与实现(毕业论文+程序源码)——FTP客户端