在我们实际开发中会接触到树形结构,根节点子节点, 然后添加数据构成了我们的树形结构, 在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)); } }
结果同上