Java树形结构设计与开发
树形结构是我们开发过程中经常遇到的一种数据结构
例如:权限树,菜单树,分类树……
数据表结构
其数据库设计大多如下:
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树形结构设计与开发相关推荐
- openjweb1.8 java web应用快速开发平台产品白皮书
因图片较多,需要图片请到资源中下载,不需要资源分. OpenJWeb(1.8) Java Web应用快速开发平台 产品白皮书 编者:OpenJWeb ...
- java ee web高级,Java EE Web高级开发案例
核心提示:Java EE Web高级开发案例 内容简介:<Java EE Web高级开发案例>充分体现了高等职业教育的特点,突出了理论和实践的紧密结合,以充分掌握基本技术技能和必要的基本知 ...
- OpenJWeb(1.6) Java Web应用快速开发平台技术白皮书
OpenJWeb中国开源组织(http://blog.csdn.net/baozhengw) 苏州创智科技有限公司(http://www.cmissoft.com) QQ:29803446 Msn:b ...
- MPSDK4J 是JAVA微信公平台开发SDK,没有复杂的功能,一切源于微信API,愿你会喜欢使用。-- 题记
MPSDK4J 是JAVA微信公平台开发SDK,没有复杂的功能,一切源于微信API,愿你会喜欢使用.-- 题记 1.介绍 MPSDK4J,非常直观的阐述了此项目的意义所在.没错,它就是JAVA语言环境 ...
- Java 8 失宠!开发人员向 Java 11 转移...
以下文章来源方志朋的博客,回复"666"获面试宝典 作者:白开水 来源:OSC开源社区(ID:oschina2013) 此前的 Java 社区报告曾指出,Java 8 仍是开发人员 ...
- 《Java EE企业级应用开发》,《分布式爬虫》等书包邮送50本!企业开发利器!...
来给大家送一波福利,这次联系了10个好友一起给各位送书,每个号送 5 本,一共 50本,还包邮哦. 感谢传智播客对本次活动的赞助. 金主介绍:传智播客是国内数一数二的IT培训机构,现在关注传智播客 ...
- 慕课网_《Java微信公众号开发进阶》学习总结
时间:2017年08月12日星期六 说明:本文部分内容均来自慕课网.@慕课网:http://www.imooc.com 教学源码:http://img.mukewang.com/down/... 学习 ...
- java微信公众号开发token验证失败的问题及解决办法
java微信公众号开发token验证失败的问题及解决办法 参考文章: (1)java微信公众号开发token验证失败的问题及解决办法 (2)https://www.cnblogs.com/beardu ...
- 是否可以将Java 8用于Android开发?
本文翻译自:Is it possible to use Java 8 for Android development? Searching the web, it is not clear if Ja ...
最新文章
- linux下clone一直运行,如何在Linux上使用clone()创建真正的线程?
- win10x64下的redis安装与使用
- EasyUI DateTimeBox设置默认时间的注意点
- react打包后图片丢失_如何快速构建React组件库
- 餐饮创业想赚钱,这5个思维方式少不了
- pythoninit_Python __init__.py文件的作用
- ajax跨域,json,jsonp
- 《Unity3d-控制枪口的朝向代码》
- EditorUtility.SetDirty 设置已改变
- 单片机最小系统着实让人着迷
- 【小程序】小程序安卓,ios,ipad兼容问题
- 南非SABS EMC CoC简介
- 如何搭建自己的微信公众号?
- word的奇葩功能--隐藏文字
- 计算机上面的按键作用,鼠标侧键有什么用 鼠标上各按键的功能是什么
- 保留核心,刺激进步 ——读《马克思传》有感
- mysql sql并列排名_教你用SQL实现统计排名
- eclipse的工作空间如何复制
- Selenium Webdriver重新使用已打开的浏览器实例
- 素材火官网后台模板下载
热门文章
- RJ45以太网接口EMC设计方案
- The server time zone value ‘й‘ is unrecognized or represents more than one time zone
- 关系数据模型和SQL基础
- Unable to resolve dependency for :app@debug/compileClasspath': Could not resolve com.***问题解决
- activeroot翻译,Active翻译
- 《第6章-GCN的性质》学习笔记
- 高性能电工电子电拖及自动化技术实训与考核装置
- 国际标准分类法ICS
- 2019,我的工作寻找之路
- C#毕业设计——基于C#+asp.net+FTP的FTP客户端设计与实现(毕业论文+程序源码)——FTP客户端