目录
概述
Hutool 是一个小而全的 Java 工具类库,通过静态方法封装,降低相关 API 的学习成本,提高工作效率,使 Java 拥有函数式语言般的优雅。
构建菜单树是我们常用的功能,构建树的时候就考虑,能不能写一个公共的类,用来构建树,思索了许久,没有想到能实现的方法。学习hutool源码也是看hutool作者如何解决问题的,也是了解优秀的设计思想。
(这是第一篇关于源码的文章,也想在这分享自己见解,这并不是一篇专门写源码的文章,难免有不足之处,多谢谅解)
hutool源码仓库:https://gitee.com/loolly/hutool.git hutool参考文档:https://hutool.cn/docs/#/
考虑的问题
构建树实际上就是 传入数据-->加工 的过程。
问题1:传入的数据对象是未知的,如何去组织相关对象。
答:hutool是将构建tree需要的核心属性,提取出来。也就是这些属性是用户必须指定的,其它属性使用Map存储(也就是用户自己定义的)
用户提交的数据最后都对转换成Tree类,并且属性都存入map中(Tree类继承了LinkedHashMap)
下图:hutool核心类
hutool具体实现
1、下面是hutool管网提供的安例。
传入数据也用其提供的默认模板
List<TreeNode<String>> nodeList = CollUtil.newArrayList();
TreeNode<String> node = new TreeNode<>("1", "0", "系统管理", 5);
nodeList.add(new TreeNode<>("1", "0", "系统管理", 5));
nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222));
nodeList.add(new TreeNode<>("111", "11", "用户添加", 0));
nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1));
nodeList.add(new TreeNode<>("21", "2", "商品管理", 44));
nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2));
// 0表示最顶层的id是0
List<Tree<String>> treeList = TreeUtil.build(nodeList, "0");
2、TreeUtil.build()做了什么
ps:
- TreeNodeCofig作用:在上边Tree核心类中知道有6个关健参数,也是存放在Map中的,这个参数就是存放关键参数key的定义。
·
- new DefaultNodeParser<> 适配器:在问题中提到用户传入的数据最后要转换成Tree对象,这里实际上是用户自定义的,案例中我们是用的默认模板,所以不需要自己定义(如果不用默认模板,就要实现NodeParser接口。
setId实际上是向map存放数据,而具体的key是由提供的TreeNodeConfig参数定义的。
3、build()方法
现在进入build()方法
public static <T, E> List<Tree<E>> build(List<T> list, E parentId, TreeNodeConfig treeNodeConfig, NodeParser<T, E> nodeParser) {
final List<Tree<E>> treeList = CollUtil.newArrayList();
Tree<E> tree;
/*
将一个T转换成Tree,只有Tree知道自己的六个属性在T中的字段名
idKey = "id";
private String parentIdKey = "parentId";
private String weightKey = "weight";
private String nameKey = "name";
private String childrenKey = "children";
// 可以配置递归深度 从0开始计算 默认此配置为空,即不限制
private Integer deep;
*/
/**
* 这里是唯一具有扩展性的
*/
for (T obj : list) {
//给tree的treeNodeConfig赋值
tree = new Tree<>(treeNodeConfig);
//将obj的六个相关属性赋值到tree,这个适配器完全是手写的
//nodeParser是一个implements NodeParser<TreeNode<T>, T>接口的类,如果我们提交的节点不是默认的,这里传新的接口
nodeParser.parse(obj, tree);
treeList.add(tree);
}
//现在就是多个tree有组织的组装在一起
List<Tree<E>> finalTreeList = CollUtil.newArrayList();
for (Tree<E> node : treeList) {
if (ObjectUtil.equals(parentId,node.getParentId())) {
//如果等于parentId代表是最外层节点
finalTreeList.add(node);
//它也是用的递归
innerBuild(treeList, node, 0, treeNodeConfig.getDeep());
}
}
// 内存每层已经排过了 这是最外层排序
finalTreeList = finalTreeList.stream().sorted().collect(Collectors.toList());
return finalTreeList;
}
ps:这段代码很简单。
- nodeParser.parse(obj, tree); 这就是调用我们提供的适配器,将传入T 类型对象,转换成Tre
4、innerBuild()方法
这里的构建过程,就很常见了,
/**
* 递归处理
*
* @param treeNodes 数据集合
* @param parentNode 当前节点
* @param deep 已递归深度
* @param maxDeep 最大递归深度 可能为null即不限制
*/
private static <T> void innerBuild(List<Tree<T>> treeNodes, Tree<T> parentNode, int deep, Integer maxDeep) {
if (CollUtil.isEmpty(treeNodes)) {
return;
}
//maxDeep 可能为空(为空时就是不管深度),如果深度达到要求,返回
if (maxDeep != null && deep >= maxDeep) {
return;
}
// 每层排序 TreeNodeMap 实现了Comparable接口
//stream().sorted()进行排序,需要该类实现 Comparable 接口,该接口只有一个方法需要实现
//interface Node<T> extends Comparable<Node<T>>他的父结点继承了
treeNodes = treeNodes.stream().sorted().collect(Collectors.toList());
for (Tree<T> childNode : treeNodes) {
//如果当前结点为,是parentNode的子结点,要加
if (parentNode.getId().equals(childNode.getParentId())) {
List<Tree<T>> children = parentNode.getChildren();
if (children == null) {
children = CollUtil.newArrayList();
parentNode.setChildren(children);
}
children.add(childNode);
// childNode.setParentId(parentNode.getId());
//设置父结点
childNode.setParent(parentNode);
innerBuild(treeNodes, childNode, deep + 1, maxDeep);
}
}
}
学到了什么:
读代码要大胆猜测其功能,如果不知道其功能,就不会理解每段代码的作用。
写一个通用的构建树工具,重点就是将传入的对象组织起来,而hutool先将核心属性提取出来,让用户自己提供 传入对象 与 核心属性的映射,这就解决了 传入对象 未知的问题,剩下的就可以用递归等方法构建树。
构建树时可以使用HashMap来取代递归,递归随着结点的增加,时间成功越来越高,因为每个结点都要遍历一遍数据。而HashMap只需要执行一次遍历
如下图:这是在其他地方截取的一段使用HashMap构建树代码,核心就是用hashMap存放每一个结点,如果要加入结点,直接到hashmap中取它的父结点就可以了。
//只有密集架才能去构树
List<EbyPlace> lists = ebyPlaceService.selectByIdAndAncestors(id);
if(lists == null || lists.size() == 0) return false;
HashMap<Long,TakeStockVo> treeHM = new HashMap<>();
for (EbyPlace list : lists) {
Long placeId = list.getPlaceId();
Long parentId = list.getParentId();
TakeStockVo takeStockVo = TakeStockVo.builder().id(placeId).fla(-5).msg(getPlaceMsg(placeId,-4)).state("-1").child(new ArrayList<TakeStockVo>()).build();
if(treeHM.containsKey(parentId)){
treeHM.get(parentId).getChild().add(takeStockVo);
}else {
treeHM.put(parentId,TakeStockVo.builder().child(new ArrayList<>()).build());
}
if(treeHM.containsKey(placeId)){
takeStockVo.setChild(treeHM.get(placeId).getChild());
treeHM.put(placeId,takeStockVo);
}else {
treeHM.put(placeId,takeStockVo);
}
if(placeId == id){
top = takeStockVo;
basetakeStockVo = takeStockVo;
}
}