在我们实际开发中会接触到树形结构,根节点子节点, 然后添加数据构成了我们的树形结构, 在Java后台利用递归思路进行构建树形结构数据,返回给前端,能以下拉菜单等形式进行展示, 以某市行政区为例

图:

一、java构建树形结构实现一

(1)、实体类

@Data
public class TreeNode {

    /** 节点ID */
    private Integer id;

    /** 父节点ID:顶级节点为0 */
    private Integer parentId;

    /** 节点名称 */
    private String label;

    /** 子节点 */
    private List<TreeNode> children;
    /** 构造函数 */
    public TreeNode(Integer id, Integer parentId, String label) {
        this.id = id;
        this.parentId = parentId;
        this.label = label;
    }
}

(2)、实现类

实现步骤:

(1)、获取所有根节点

(2)、遍历所有根节点,递归构建子树形结构

@Service
public class TreeBuild {

    // 保存参与构建树形的所有数据(通常数据库查询结果)
    public List<TreeNode> nodeList = new ArrayList<>();

    /**
     *  构造方法
     *  @param nodeList 将数据集合赋值给nodeList,即所有数据作为所有节点。
     */
    public TreeBuild(List<TreeNode> nodeList){
        this.nodeList = nodeList;
    }

    /**
     *   获取需构建的所有根节点(顶级节点) "0"
     *   @return 所有根节点List集合
     */
    public List<TreeNode> getRootNode(){
        // 保存所有根节点(所有根节点的数据)
        List<TreeNode> rootNodeList = new ArrayList<>();
        // treeNode:查询出的每一条数据(节点)
        for (TreeNode treeNode : nodeList){
            // 判断当前节点是否为根节点,此处注意:若parentId类型是String,则要采用equals()方法判断。
            if (0 == treeNode.getParentId()) {
                // 是,添加
                rootNodeList.add(treeNode);
            }
        }
        return rootNodeList;
    }

    /**
     *  根据每一个顶级节点(根节点)进行构建树形结构
     *  @return  构建整棵树
     */
    public List<TreeNode> buildTree(){
        // treeNodes:保存一个顶级节点所构建出来的完整树形
        List<TreeNode> treeNodes = new ArrayList<TreeNode>();
        // getRootNode():获取所有的根节点
        for (TreeNode treeRootNode : getRootNode()) {
            // 将顶级节点进行构建子树
            treeRootNode = buildChildTree(treeRootNode);
            // 完成一个顶级节点所构建的树形,增加进来
            treeNodes.add(treeRootNode);
        }
        return treeNodes;
    }

    /**
     *  递归-----构建子树形结构
     *  @param  pNode 根节点(顶级节点)
     *  @return 整棵树
     */
    public TreeNode buildChildTree(TreeNode pNode){ // 调用一次该方法就相当于给TreeNode对象添加children属性
        List<TreeNode> childTree = new ArrayList<TreeNode>();
        // nodeList:所有节点集合(所有数据)
        for (TreeNode treeNode : nodeList) {
            // 判断当前节点的父节点ID是否等于根节点的ID,即当前节点为其下的子节点
            if (treeNode.getParentId().equals(pNode.getId())) {
                // 再递归进行判断当前节点的情况,调用自身方法
                childTree.add(buildChildTree(treeNode));
            }
        }
        // for循环结束,即节点下没有任何节点,树形构建结束,设置树结果
        pNode.setChildren(childTree);
        return pNode;
    }
}

(3)、测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class Test2 {
    @Test
    public void test1() {
        // 模拟测试数据(通常为数据库的查询结果)
        List<TreeNode> treeNodeList = new ArrayList<>();
        treeNodeList.add(new TreeNode(1, 0, "顶级节点A"));
        treeNodeList.add(new TreeNode(2, 0, "顶级节点B"));
        treeNodeList.add(new TreeNode(3, 1, "父节点是A"));
        treeNodeList.add(new TreeNode(4, 2, "父节点是B"));
        treeNodeList.add(new TreeNode(5, 2, "父节点是B"));
        treeNodeList.add(new TreeNode(6, 3, "父节点的ID是3"));

        // 创建树形结构(数据集合作为参数)
        TreeBuild treeBuild = new TreeBuild(treeNodeList);
        // 原查询结果转换树形结构
        treeNodeList = treeBuild.buildTree();
        System.out.println(JSON.toJSONString(treeNodeList));
    }
}

结果:

[
    {
        "children": [
            {
                "children": [
                    {
                        "children": [ ], 
                        "id": 6, 
                        "label": "父节点的ID是3", 
                        "parentId": 3
                    }
                ], 
                "id": 3, 
                "label": "父节点是A", 
                "parentId": 1
            }
        ], 
        "id": 1, 
        "label": "顶级节点A", 
        "parentId": 0
    }, 
    {
        "children": [
            {
                "children": [ ], 
                "id": 4, 
                "label": "父节点是B", 
                "parentId": 2
            }, 
            {
                "children": [ ], 
                "id": 5, 
                "label": "父节点是B", 
                "parentId": 2
            }
        ], 
        "id": 2, 
        "label": "顶级节点B", 
        "parentId": 0
    }
]

二、java构建树形结构实现二

(1)、实体类

@Data
public class TreeSelect implements Serializable
{

    /** 节点ID */
    private Long id;

    /** 节点名称 */
    private String label;

    /** 父ID */
    private Long parentId;

    /** 子节点 */
    private List<TreeSelect> children;

    public TreeSelect(Long id, String label, Long parentId) {
        this.id = id;
        this.label = label;
        this.parentId = parentId;
    }

}

(2)、实现类

@Service
public class TreeSelectBuild {
    /**
     * 构建前端所需要树结构
     *
     * @param trees 列表
     * @return 树结构列表
     */
    public static List<TreeSelect> buildDeptTree(List<TreeSelect> trees) {
        List<TreeSelect> returnList = new ArrayList<TreeSelect>();
        List<Long> tempList = new ArrayList<Long>();
        for (TreeSelect dept : trees) {
            tempList.add(dept.getId()); // 所有数据的id
        }
        // for循环:表达式1:循环变量赋初值,表达式2:循环条件
        for (Iterator<TreeSelect> iterator = trees.iterator(); iterator.hasNext();) {
            TreeSelect treeSelect = (TreeSelect) iterator.next();
            // 如果是顶级节点, 遍历该父节点的所有子节点
            if (!tempList.contains(treeSelect.getParentId())) { // 筛选出所有顶级节点
                recursionFn(trees, treeSelect);
                returnList.add(treeSelect);
            }
        }
        if (returnList.isEmpty()) {
            returnList = trees;
        }
        return returnList;
    }
    /**
     * 递归列表,每执行一次该方法就向第二个参数TreeSelect设置属性值children
     */
    private static void recursionFn(List<TreeSelect> list, TreeSelect t) { // TreeSelect为顶级节点
        // 得到子节点列表
        List<TreeSelect> childList = getChildList(list, t);
        t.setChildren(childList);
        for (TreeSelect tChild : childList) { // 给每个tChild设置children属性值
            if (hasChild(list, tChild)) {
                recursionFn(list, tChild);
            }
        }
    }

    /**
     * 得到子节点列表
     */
    private static List<TreeSelect> getChildList(List<TreeSelect> list, TreeSelect t) {
        List<TreeSelect> tlist = new ArrayList<TreeSelect>();
        for (TreeSelect treeSelect : list) {
            if (null != treeSelect.getParentId() && treeSelect.getParentId().longValue() == t.getId().longValue()) {
                tlist.add(treeSelect);
            }
        }
        return tlist;
    }

    /**
     * 判断是否有子节点
     */
    private static boolean hasChild(List<TreeSelect> list, TreeSelect t) {
        return getChildList(list, t).size() > 0;
    }
}

(3)、测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class Test3 {
    @Autowired
    private TreeSelectBuild treeSelectBuild;
    @Test
    public void test1() {
        List<TreeSelect> treeSelects = new ArrayList<>();
        treeSelects.add(new TreeSelect(1l, "顶级节点A",0l));
        treeSelects.add(new TreeSelect(2l, "顶级节点B",0l));
        treeSelects.add(new TreeSelect(3l, "父节点是A",1l));
        treeSelects.add(new TreeSelect(4l, "父节点是B",2l));
        treeSelects.add(new TreeSelect(5l, "父节点是B",2l));
        treeSelects.add(new TreeSelect(6l, "父节点的ID是3",3l));

        List<TreeSelect> treeSelects1 = treeSelectBuild.buildDeptTree(treeSelects);
        System.out.println(JSON.toJSONString(treeSelects1));
    }
}

结果同上

三、java构建树形结构实现三

这种方式的特点是声明一个map,用来过滤已操作过的数据。

(1)、实体类

@Data
public class Menu {
    private Long id;

    /**
     * 上级ID
     */
    private Long parentId;

    /**
     * 名称
     */
    private String name;

    private List<Menu> children;

    public Menu(Long id, String name, Long parentId) {
        this.id = id;
        this.parentId = parentId;
        this.name = name;
    }
}

(2)、实现类

@Service
public class MenuTreeBuild {

    public List<Menu> buildTree(List<Menu> list) {
        //封装成树形数据
        //最顶层根节点
        List<Menu> rootList = new ArrayList<>();
        //非最顶层根节点
        List<Menu> bodyList = new ArrayList<>();
        for (Menu menu : list) {
            if (menu.getParentId() == 0) {
                rootList.add(menu);
            } else {
                bodyList.add(menu);
            }
        }
        if (bodyList.size() > 0) {
            // 声明一个map,用来过滤已操作过的数据
            Map<String, Long> map = new HashMap<>(bodyList.size());
            // 遍历所有根节点,构建子树形结构
            rootList.forEach(parent -> getChild(parent, bodyList, map));
        }
        return rootList;
    }
    /**
     * 获取孩子节点
     *
     * @param parent
     * @param bodyList
     * @param map
     */
    private void getChild(Menu parent, List<Menu> bodyList, Map<String, Long> map) {
        List<Menu> childList = new ArrayList<>();
        bodyList.stream().filter(c -> !map.containsKey(c.getId()))
                .filter(c -> c.getParentId().equals(parent.getId()))
                .forEach(c -> {
                    map.put(c.getId() + "", c.getParentId());
                    getChild(c, bodyList, map);
                    childList.add(c);
                });
        parent.setChildren(childList);
    }
}

(3)、测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class Test6 {
    @Autowired
    private MenuTreeBuild menuTreeBuild;
    @Test
    public void test1() {
        List<Menu> treeSelects = new ArrayList<>();
        treeSelects.add(new Menu(1l, "顶级节点A",0l));
        treeSelects.add(new Menu(2l, "顶级节点B",0l));
        treeSelects.add(new Menu(3l, "父节点是A",1l));
        treeSelects.add(new Menu(4l, "父节点是B",2l));
        treeSelects.add(new Menu(5l, "父节点是B",2l));
        treeSelects.add(new Menu(6l, "父节点的ID是3",3l));
        List<Menu> menus = menuTreeBuild.buildTree(treeSelects);
        System.out.println(JSON.toJSONString(menus));
    }
}

结果同上

四、利用java8 Stream流实现树形结构(推荐)

(1)、实体类

@Data
public class TreeSelect implements Serializable
{

    /** 节点ID */
    private Long id;

    /** 节点名称 */
    private String label;

    /** 父ID */
    private Long parentId;

    /** 子节点 */
    private List<TreeSelect> children;

    public TreeSelect(Long id, String label, Long parentId) {
        this.id = id;
        this.label = label;
        this.parentId = parentId;
    }

}

(2)、实现类

@Service
public class TreeSelectBuild {public static List<TreeSelect> buildDeptTreeByStream(List<TreeSelect> trees){
        //获取parentId = 0的根节点
        List<TreeSelect> list = trees.stream().filter(item -> item.getParentId() == 0L).collect(Collectors.toList());
        //根据parentId进行分组
        Map<Long, List<TreeSelect>> map = trees.stream().collect(Collectors.groupingBy(TreeSelect::getParentId));
        recursionFnTree(list, map);
        return list;
    }

    /**
     * 递归遍历节点设置children属性,参数一为上一级节点的集合,参数二为根据parentId进行分组后的map
     * @param list
     * @param map
     */
    public static void recursionFnTree(List<TreeSelect> list, Map<Long, List<TreeSelect>> map){
        for (TreeSelect treeSelect : list) { // 遍历所有根节点
            List<TreeSelect> childList = map.get(treeSelect.getId()); // 根据根节点的ID得到子节点
            treeSelect.setChildren(childList); // 设置children属性值
            if (null != childList && 0 < childList.size()){
                recursionFnTree(childList,map);
            }
        }
    }
}

(3)、测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class Test4 {
    @Autowired
    private TreeSelectBuild treeSelectBuild;
    @Test
    public void test1() {
        List<TreeSelect> treeSelects = new ArrayList<>();
        treeSelects.add(new TreeSelect(1l, "顶级节点A",0l));
        treeSelects.add(new TreeSelect(2l, "顶级节点B",0l));
        treeSelects.add(new TreeSelect(3l, "父节点是A",1l));
        treeSelects.add(new TreeSelect(4l, "父节点是B",2l));
        treeSelects.add(new TreeSelect(5l, "父节点是B",2l));
        treeSelects.add(new TreeSelect(6l, "父节点的ID是3",3l));

        List<TreeSelect> treeSelects1 = treeSelectBuild.buildDeptTreeByStream(treeSelects);
        System.out.println(JSON.toJSONString(treeSelects1));
    }
}

结果同上

五、Stream流升级实现树形结构

(1)、实体类

@Data
public class TreeSelect implements Serializable
{

    /** 节点ID */
    private Long id;

    /** 节点名称 */
    private String label;

    /** 父ID */
    private Long parentId;

    /** 子节点 */
    private List<TreeSelect> children;

    public TreeSelect(Long id, String label, Long parentId) {
        this.id = id;
        this.label = label;
        this.parentId = parentId;
    }

}

(2)、实现类

@Service
public class TreeSelectBuild {public static List<TreeSelect> buildTree(List<TreeSelect> trees){
        //获取父节点
        List<TreeSelect> collect = trees.stream().filter(m -> m.getParentId() == 0).map(
                (m) -> {
                    m.setChildren(getChildrenList(m, trees));
                    return m;
                }
        ).collect(Collectors.toList());
        return collect;
    }


    /**
     * 获取子节点列表
     * @param tree
     * @param list
     * @return
     */
    public static List<TreeSelect> getChildrenList(TreeSelect tree, List<TreeSelect> list){
        List<TreeSelect> children = list.stream().filter(item -> Objects.equals(item.getParentId(), tree.getId())).map(
                (item) -> {
                    item.setChildren(getChildrenList(item, list));
                    return item;
                }
        ).collect(Collectors.toList());
        return children;
    }

}

(3)、测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class Test5 {
    @Autowired
    private TreeSelectBuild treeSelectBuild;
    @Test
    public void test1() {
        List<TreeSelect> treeSelects = new ArrayList<>();
        treeSelects.add(new TreeSelect(1l, "顶级节点A",0l));
        treeSelects.add(new TreeSelect(2l, "顶级节点B",0l));
        treeSelects.add(new TreeSelect(3l, "父节点是A",1l));
        treeSelects.add(new TreeSelect(4l, "父节点是B",2l));
        treeSelects.add(new TreeSelect(5l, "父节点是B",2l));
        treeSelects.add(new TreeSelect(6l, "父节点的ID是3",3l));
        List<TreeSelect> treeSelects1 = treeSelectBuild.buildTree(treeSelects);
        System.out.println(JSON.toJSONString(treeSelects1));
    }
}

结果同上

 

posted on 2023-02-27 16:55  周文豪  阅读(1740)  评论(0编辑  收藏  举报