Java通用树状数据工具类

2,776 阅读3分钟

前言

最近一直在搞权限这一块的业务,说到权限就离不开菜单这一块的内容,菜单是天生的树状结构,说到构建树将会想到递归,说到递归就开始 头疼,一头疼就开始掉头发,再掉头发就要秃了......算了不说了,都是泪

来源

本代码的原版来及网络,经过本人精心挑选(Google)以后,选中的代码,使用Java8 Stream的方式极大的简化代码,但是没有使用并行流,因为并行流并不一定会提升效率,反而可能出现并发,引发奇怪的问题,这个大家后续可以考虑使用,并通知我效果咋样.源代码比较贴近业务,现在的工具类是在这个基础上抽象的代码,抛砖引玉,看看大家有没有更好的实现,也帮忙找找BUG,吐槽一下

代码

相信作为程序员的大家已经开始吐槽,没代码你是说个XX,好了,好了,上代码

   ```
public class TreeUtil {
   //你的菜单VO或者PO需要实现这个接口,泛型是限定children的,子级结构
   //泛型一定要表示为VO本身,下面工具类会限制入口
   public interface TreeVoFeature<T extends TreeVoFeature<T>> {

       /**
        * 可设置子节点
        *
        * @param children 子节点
        */
       void setChildren(List<T> children);

       /**
        * 暴露当前节点
        *
        * @return 当前节点标识
        */
       String gainNodeId();

       /**
        * 暴露父级节点
        *
        * @return 父级节点
        */
       String gainParentNodeId();

   }

   //暴露的工具方法,入口泛型做了很多限制
   //并且暴露根节点的ID这里说明一下,根节点可以是虚拟节点,不同的系统有不同的需求
   //按照自身需求
   public static <V extends TreeVoFeature<V>> List<V> build(List<V> trees, String rootNodeId) {
       if (CollectionUtils.isEmpty(trees)) {
           return new ArrayList<>();
       }
       List<V> roots = trees.stream().filter(mt -> StringUtils.equals(mt.gainParentNodeId(), rootNodeId)).collect(Collectors.toList());
       List<V> others = trees.stream().filter(mt -> mt.gainParentNodeId() != null).collect(Collectors.toList());
       return buildTree(roots, others);
   }

   public static <V extends TreeVoFeature<V>> List<V> build(List<V> trees, List<V> leafs, String rootNodeId) {
       if (CollectionUtils.isEmpty(trees)) {
           return new ArrayList<>();
       }

       List<String> filteredIds = leafs.stream()
                                        .flatMap(leaf -> getParentsOfNode(trees, leaf.gainNodeId(), rootNodeId).stream())
                                        .distinct()
                                        .collect(Collectors.toList());
       List<V> filterTrees = trees.stream().filter(v -> filteredIds.contains(v.gainNodeId())).collect(Collectors.toList());
       return build(filterTrees, rootNodeId);
   }

   private static <V extends TreeVoFeature<V>> List<V> buildTree(List<V> roots, List<V> others) {
       if (CollectionUtils.isNotEmpty(others)) {
           //声明一个map,用来过滤已操作过的数据
           Map<String, String> map = Maps.newConcurrentMap();
           //逻辑其实很简单,从根节点往下遍历,加上所有的根节点
           //吐槽:但是架不住递归呀
           roots.forEach(beanTree -> addChildren(others, beanTree, map));
           return roots;
       }
       return Lists.newArrayList();
   }

   private static <V extends TreeVoFeature<V>> void addChildren(List<V> others, V beanTree, Map<String, String> map) {
       List<V> childList = Lists.newArrayList();
       others.stream()
             //判断是否已经被处理过了
             .filter(c -> !map.containsKey(c.gainNodeId()))
             //获取当前节点的子节点
             .filter(c -> c.gainParentNodeId().equals(beanTree.gainNodeId()))
             //子节点下所有的节点,也需要加进去
             .forEach(c -> {
                 map.put(c.gainNodeId(), c.gainParentNodeId());
                 addChildren(others, c, map);
                 //就是这里....递归...
                 childList.add(c);
             });
       beanTree.setChildren(childList);
   }

   /**
    * 叶子节点集朝上遍历
    * 获取树枝节点直到树根的buId
    */
   public static <V extends TreeVoFeature<V>> List<String> getParentsOfNode(List<V> trees, String nodeId,
                                                                            String rootNodeId) {
       Map<String, V> collectMap = trees.stream().collect(Collectors.toMap(TreeVoFeature::gainNodeId, p -> p));
       return getParentNodeByNode(nodeId, collectMap, rootNodeId);
   }

   private static <V extends TreeVoFeature<V>> List<String> getParentNodeByNode(String leafId,
                                                                                Map<String, V> collectMap,
                                                                                String rootNodeId) {
       List<String> parentNodeIdList = new ArrayList<>();
       if (leafId == null) {
           return parentNodeIdList;
       } else if (collectMap.get(leafId).gainParentNodeId().equals(rootNodeId)) {
           //根节点
           parentNodeIdList.add(leafId);
           return parentNodeIdList;
       } else {
           parentNodeIdList.add(leafId);
           parentNodeIdList.addAll(
                   getParentNodeByNode(collectMap.get(leafId).gainParentNodeId(), collectMap, rootNodeId)
           );
       }
       return parentNodeIdList;
   }
}

如上,代码逻辑其实并不复杂,在Stream的简化下,代码逻辑更加清晰,希望能够帮到大家,能让大家少掉点头发O(∩_∩)O哈哈~

回顾

其实Oracle天生具有构建树的能力(具体请Google),但是我们用的Mysql呀...所以没办法,其实树还可以从叶子节点往上往上构建, 但是更复杂,我这边也遇到这个问题,我想了个其他办法,就是通过叶子节点,把所有的节点遍历出来,再调用上述的方法,发现程序员还是 不能逼迫的,你永远不知道他会想出什么实现...如上,谢谢阅读!