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