学习hutool源码TreeUtil.build()得到了什么

 

目录

 

概述

考虑的问题

hutool具体实现

1、下面是hutool管网提供的安例。

 2、TreeUtil.build()做了什么

3、build()方法

4、innerBuild()方法

学到了什么:


概述

    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;
            }
        }

 

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值