JAVA构建树形结构通用工具类

场景说明

在日常java开发过程中,经常遇到要把一个扁平结构的数据,列入菜单,部门,区域等需要处理成为树形结构返给前台,因此可以写一个公共的util来实现。特此写一篇博客来记录解决问题的思路以及过程

文章最后贴出了完整的代码,直接复制两个类即可在项目当中使用

案例中的方法说明

用了java 8的最新特性lambda表达式,可以了解下其实很简单。
===== list.stream
吧list转化为一个流进行操作
===== list.stream.filter(c -> c.getParentId() != null)

filter 选择出满足条件的元素

构建思路实现

要处理树形结构,对应的表记录和实体类中至少满足以下条件才可以处理
:实体类或表中要包含类似如下的字段
表中结构:节点id:node 、父节点id: parentId 、节点名称:nodeName
表数据
实体类中还要多一个childrenList的字段,可以加在Vo里面
实体类,如果实体类中没有子节点字段可以单独建一个和实体类一样的vo,多一个list

第一步:构建接口TreeFeature

若要实现通用构建,则肯定需要获得实体类的parentId,nodeId,childrenList,但是每个实体类或Vo的名字不一样。怎么办呢。因此我想到的是定义一个接口,让需要构建树形结构的实体来实现。当中就有获得nodeId,设置子集的方法。接口定义如下

/**
 * 定义一个接口,如果需要构建一颗树需要实现该接口
 * 实现接口的需要传入一个泛型参数T ,表示该类实现了该接口
 */
public interface TreeFeature<T extends TreeFeature> {

    /**
     * 获取子节点
     */
    List<T> getChildrenList();

    /**
     * 获取当前节点
     */
    String findNodeId();


    /**
     * 获取父节点
     */
    String findParentId();

    /**
     * 设置子节点
     */
    void putChildrenList(List<T> children);

    /**
     * 树形结构名称
     */
    String getTreeLable();

第二步:让需要构建树形结构的类实现该接口并实现方法

public class AreaDemo implements TreeFeature {

    //区域编码
    private String baCode;

    //区域名称
    private String baName;

    //父节点
    private String baParentId;

    //子节点
    private List<Area> childrenList;

    @Override
    public List getChildrenList() {
        return this.childrenList;
    }

    @Override
    public String findNodeId() {
        return this.baCode;
    }

    @Override
    public String findParentId() {
        return this.baParentId;
    }

    @Override
    public void putChildrenList(List children) {
        this.childrenList = children;
    }

    @Override
    public String getTreeLable() {
        return this.baName;
    }

第三部创建通用的TreeUtil工具类

1.首先创建一个build方法供外部调用,接受参数为实现了TreeFeature接口的List,和一个根节点参数,返回值为构建好的list,方法先初步筛选出根节点和所有非根节点

        if(!CollectionUtils.isEmpty(allList)){
            //1.获取所有根节点
            List<V> roots = allList.stream().filter(c -> c.getParentId().equals(rootNodeId)).filter(c -> c.getParentId() == null).collect(Collectors.toList());
            //2.获取所有非根节点
            List<V> others = allList.stream().filter(c -> c.getParentId() != null).collect(Collectors.toList());
            return buildTree(roots,others);
        }
        return new ArrayList<>();
    }

2.创建buildTree方法,参数为根节点集合以及非根节点的集合,思路为遍历根节点,吧根节点下的子集加入其中

        if(!CollectionUtils.isEmpty(roots)){
            Map<String,String> map = new ConcurrentHashMap<>();
            //从根节点向下遍历
            roots.forEach(beanTree -> addChildren(others,beanTree,map));
            return roots;
        }
        return new ArrayList<>();

    }
    

3.核心关键点来了addChildren为核心方法,顾名思义就是吧子节点加入到根节点当中,因此初步确定该方法接受参数为两个,一个集合,一个根节点。另外为了减少遍历次数,传入一个记录了操作过节点的map

     * 把根节点所属的子节点加入其中
     * @param others
     * @param beanTree
     * @param map
     * @param <V>
     */
    public static <V extends TreeFeature2<V>> void addChildren(List<V> others,V beanTree,Map map){
        //定义一个子集集合,最后会调用实体的putChildrenList,给实体的childrenList赋值
        List<V> childrenList = new ArrayList<>();
        others.stream()
                //过滤集合中已经有的节点
                .filter(c->!map.containsKey(c.getNode()))
                //筛选属于传入的beanTree下的子节点,元素的parentId = beanTree.nodeID
                .filter(c->c.getNode().equals(beanTree.getParentId()))
                //至此已经从others中过滤出了beanTree下的子节点集合,开始遍历并加入childreList
                .forEach(item -> {
                    //吧该节点放入map
                    map.put(item.getNode(),item.getParentId());
                    //递归调用addChildren,继续把子节点的子集加入
                    addChildren(others,beanTree,map);
                    childrenList.add(item);
                });
        //调用beanTree的putChildrenList 给该节点赋值
        beanTree.putChildrenList(childrenList);
    }


到这位置,TreeUtil已经完成来测试,先在实体类中添加点测试数据

     * 初始化
     */
    private static List<Area> areaList = new ArrayList<>();

    static {
        areaList.add( new Area("530000","云南省","0"));

        areaList.add( new Area("530100","昆明市","530000"));
        areaList.add( new Area("530109","曲靖市","530000"));
        areaList.add( new Area("530211","麒麟区","530109"));
        areaList.add( new Area("530212","麒麟区2","530109"));

        areaList.add( new Area("530101","昆明市市辖区","530100"));
        areaList.add( new Area("530102","五华区","530100"));
        areaList.add(new Area("530102","盘龙区","530100"));

        areaList.add( new Area("110000","北京市","0"));

        areaList.add( new Area("110100","北京市辖区","110000"));
        areaList.add( new Area("110100","北京市辖区","110000"));
        areaList.add( new Area("110101","东城区","110100"));
        areaList.add( new Area("110102","西城区","110100"));


        
    }


    public Area() {
    }

    public Area(String baCode, String baName, String baParentId) {
        this.baCode = baCode;
        this.baName = baName;
        this.baParentId = baParentId;
    }

编写测试类


    public static void main(String[] args) {
        List<Area> areaList = Area.getAreaList();
        List<Area> build = TreeUtilDemo.build(areaList, "0");
        build.stream().forEach(item->{
            System.out.println(item);
        });
    }
}

输出结果

Area{baCode='110000', baName='北京市', baParentId='0', childrenList=[Area{baCode='110100', baName='北京市辖区', baParentId='110000', childrenList=[Area{baCode='110101', baName='东城区', baParentId='110100', childrenList=[]}, Area{baCode='110102', baName='西城区', baParentId='110100', childrenList=[]}]}]}

当有一个新的需要构建树形结构的实体时,只需要实现TreeFeature接口并重写好其中的方法,调用TreeUtil .build即可

完整类的代码如下

TreeVoFeature类
public interface TreeVoFeature<T extends TreeVoFeature> {
    /**
     * 设置子节点
     *
     * @param children 子节点
     */
    void putChildrenList(List<T> children);

    /**
     * 获取子节点
     *
     * @return 父级节点
     */
    List<T> getChildrenList();

    /**
     * 获取当前节点
     *
     * @return 当前节点标识
     */
    String findNodeId();

    /**
     * 获取父级节点
     *
     * @return 父级节点
     */
    String findParentNodeId();

    /**
     * 树形结构名称
     *
     * @return 树形结构名称
     */
    String getTreeLabel();
}
TreeUtil类
public class TreeUtil {

    /**
     * 构建TreeSelect
     * @param lists
     * @param <V>
     * @return
     */
    public static  <V extends TreeVoFeature<V>> List<TreeSelect> buildTreeSelect(List<V> lists) {
        return addChildren(lists);
    }

    /**
     * 递归TreeSelect
     * @param lists
     * @param <V>
     * @return
     */
    private static <V extends TreeVoFeature<V>> List<TreeSelect> addChildren(List<V> lists) {
        List<TreeSelect> treeSelects = Lists.newArrayList();
        lists.stream().forEach(c -> {
            TreeSelect treeSelect = new TreeSelect();
            treeSelect.setId(Long.valueOf(c.findNodeId()));
            treeSelect.setLabel(c.getTreeLabel());
            treeSelect.setChildren(addChildren(c.getChildrenList()));
            treeSelects.add(treeSelect);
        });
        return treeSelects;
    }

    /**
     * 构建树形结构
     * @param trees
     * @param rootNodeId
     * @param <V>
     * @return
     */
    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(node -> node.findParentNodeId().equals(rootNodeId)).collect(Collectors.toList());
        //其他节点
        List<V> others = trees.stream().filter(node -> node.findParentNodeId() != null).collect(Collectors.toList());
        return buildTree(roots, others);
    }

    /**
     * 生成树形结构
     * @param roots 根节点
     * @param others 子节点
     * @param <V>
     * @return
     */
    private static <V extends TreeVoFeature<V>> List<V> buildTree(List<V> roots, List<V> others) {
        if (!CollectionUtils.isEmpty(others)) {
            //声明一个map,用来过滤已操作过的数据
            Map<String, String> map = Maps.newConcurrentMap();
            //逻辑其实很简单,从根节点往下遍历,加上所有的根节点
            roots.forEach(beanTree -> addChildren(others, beanTree, map));
            return roots;
        }
        return Lists.newArrayList();
    }

    /**
     * 获取子节点列表
     * @param others 所有子节点
     * @param beanTree  根节点
     * @param map 过滤已加入过的节点
     * @param <V>
     */
    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.findNodeId()))
                //获取当前节点的子节点
                .filter(c -> c.findParentNodeId().equals(beanTree.findNodeId()))
                //子节点下所有的节点,也需要加进去
                .forEach(c -> {
                    map.put(c.findNodeId(), c.findParentNodeId());
                    addChildren(others, c, map);
                    childList.add(c);
                });
        beanTree.putChildrenList(childList);
    }
}
  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值