下面的第一章节为构建树的原理,往下拉到文章末尾有工具类可以直接使用
构建树的原理:
先看看模拟的数据库的值(数据库名:company(公司))
可以根据parentId为0的数据为父节点,但是有些数据库根据不同的情况,设置了不同属性
level(等级1为顶点) | id | parentId(父公司Id) | name(公司名) |
---|---|---|---|
1 | 1 | 0 | 顶级节点A |
1 | 2 | 0 | 顶级节点B |
2 | 3 | 1 | 父节点是A |
2 | 4 | 2 | 父节点是B |
2 | 5 | 2 | 父节点是B |
3 | 6 | 3 | 父节点的ID是3 |
递归构建树
构建一棵树的步骤
1、首先获取所有的根节点(顶级节点),跟数据库的配置有关
2、根据每一个根节点,与所有节点集合(数据)进行判断,当前节点是否为其下的子节点。
3、若是,则递归调用构建树形;若不是,则表明该节点不属于其下子节点。
4、应继续循环判断节点父子关系,直到所有节点与根节点判断完毕。
/**
* 构造树(需要根据不同的情况变动)
* @return
*/
private List<TreeNode> constructionTree(List<Company> list) {
// 全部数据(由查找的数据遍历得到)
List<TreeNode> allList = new ArrayList<>();
// 根节点的数据
List<TreeNode> rootNodeList = new ArrayList<>();
// 最终的树结构
List<TreeNode> treeList = new ArrayList<>();
// 获取根节点
list.forEach(x -> {
// 这里就是说根节点是级别为1的值
if ("1".equals(x.getLevel())) {
TreeNode treeNode = new TreeNode()
.setKey(x.getId())
.setId(x.getId())
.setTitle(x.getName())
.setParentId(x.getParentId());
rootNodeList.add(treeNode);
}
// 构造树结构,这里可以执行过滤操作,把list(List<Data>)过滤成我们需要的参数(allList)
TreeNode treeNode = new TreeNode()
.setKey(x.getId())
.setId(x.getId())
.setTitle(x.getName())
.setParentId(x.getParentId());
allList.add(treeNode);
});
// 装配树干
for (TreeNode treeRootNode : rootNodeList) {
// 将顶级节点进行构建子树
treeRootNode = buildChildTree(treeRootNode,allList);
// 完成一个顶级节点所构建的树形,增加进来
treeList.add(treeRootNode);
}
return treeList;
}
/**
* 递归-----构建子树形结构
* @param pNode 根节点(顶级节点)
* @param treeList 全部数据
* @return 整棵树
*/
public TreeNode buildChildTree(TreeNode pNode, List<TreeNode> treeList){
List<TreeNode> childTree = new ArrayList<>();
// nodeList:所有节点集合(所有数据)
for (TreeNode treeNode : treeList) {
// 判断当前节点的父节点ID是否等于根节点的ID,即当前节点为其下的子节点
if (treeNode.getParentId().equals(pNode.getId())) {
// 再递归进行判断当前节点的情况,调用自身方法
childTree.add(buildChildTree(treeNode,treeList));
}
}
// for循环结束,即节点下没有任何节点,树形构建结束,设置树结果
pNode.setChildren(childTree);
return pNode;
}
TreeNode(树节点类)(用于封装转给前端),里面的值可以改动
/**
* TreeNode 树节点
*
* @author ZPA
* @date 2023年10月23日
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class TreeNode {
/** ID(前端需要对应公司的id) */
private String key;
/** 节点id */
private String id;
/** 父节点id:顶级节点为 0 */
private String parentId;
/** 节点名称(前端需要对应公司的name) */
private String title;
/** 子节点 */
private List<TreeNode> children;
}
Company(实体类)
/**
* 模拟公司
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Company {
/**
* 公司等级
*/
private String level;
/**
* 公司ID
*/
private String id;
/**
* 父公司ID
*/
private String parentId;
/**
* 公司名字
*/
private String name;
}
测试
public class TreeTest {
@Test
public void treeTest(){
// 模拟测试数据(模拟数据库的值)
List<Company> companyList = new ArrayList<>();
companyList.add(new Company("1","1","0","顶级节点A"));
companyList.add(new Company("1","2","0","顶级节点B"));
companyList.add(new Company("2","3","1","父节点是A"));
companyList.add(new Company("2","4","2","父节点是B"));
companyList.add(new Company("2","5","2","父节点是B"));
companyList.add(new Company("3","6","3","父节点的ID是3"));
TreeTest test = new TreeTest();
// 原查询结果转换树形结构
List<TreeNode> treeNodes = test.constructionTree(companyList);
System.out.println(treeNodes);
}
/**
* 构造树(需要根据不同的情况变动)
* @return
*/
private List<TreeNode> constructionTree(List<Company> list) {
// 全部数据(由查找的数据遍历得到)
List<TreeNode> allList = new ArrayList<>();
// 根节点的数据
List<TreeNode> rootNodeList = new ArrayList<>();
// 最终的树结构
List<TreeNode> treeList = new ArrayList<>();
// 获取根节点
list.forEach(x -> {
// 这里就是说根节点是级别为1的值
if ("1".equals(x.getLevel())) {
TreeNode treeNode = new TreeNode()
.setKey(x.getId())
.setId(x.getId())
.setTitle(x.getName())
.setParentId(x.getParentId());
rootNodeList.add(treeNode);
}
});
// 构造树结构,这里可以执行过滤操作,把list(List<Data>)过滤成我们需要的参数(allList)
list.forEach(x->{
TreeNode treeNode = new TreeNode()
.setKey(x.getId())
.setId(x.getId())
.setTitle(x.getName())
.setParentId(x.getParentId());
allList.add(treeNode);
});
// 装配树干
for (TreeNode treeRootNode : rootNodeList) {
// 将顶级节点进行构建子树
treeRootNode = buildChildTree(treeRootNode,allList);
// 完成一个顶级节点所构建的树形,增加进来
treeList.add(treeRootNode);
}
return treeList;
}
/**
* 递归-----构建子树形结构
* @param pNode 根节点(顶级节点)
* @param treeList 全部数据
* @return 整棵树
*/
public TreeNode buildChildTree(TreeNode pNode, List<TreeNode> treeList){
List<TreeNode> childTree = new ArrayList<>();
// nodeList:所有节点集合(所有数据)
for (TreeNode treeNode : treeList) {
// 判断当前节点的父节点ID是否等于根节点的ID,即当前节点为其下的子节点
if (treeNode.getParentId().equals(pNode.getId())) {
// 再递归进行判断当前节点的情况,调用自身方法
childTree.add(buildChildTree(treeNode,treeList));
}
}
// for循环结束,即节点下没有任何节点,树形构建结束,设置树结果
pNode.setChildren(childTree);
return pNode;
}
}
结果打印
[
{
"children": [
{
"children": [
{
"children": [],
"id": "6",
"key": "6",
"parentId": "3",
"title": "父节点的ID是3"
}
],
"id": "3",
"key": "3",
"parentId": "1",
"title": "父节点是A"
}
],
"id": "1",
"key": "1",
"parentId": "0",
"title": "顶级节点A"
},
{
"children": [
{
"children": [],
"id": "4",
"key": "4",
"parentId": "2",
"title": "父节点是B"
},
{
"children": [],
"id": "5",
"key": "5",
"parentId": "2",
"title": "父节点是B"
}
],
"id": "2",
"key": "2",
"parentId": "0",
"title": "顶级节点B"
}
]
构建树工具类
public class TreeUtils {
private TreeUtils() {
}
/**
* @param collection 集合
* @param getId 子节点
* @param getParentId 父节点
* @param setNode 设置子集合
* @param <E> 集合的类型
* @param <R> 节点属性的类型
* @return Collection
*/
public static <E, R> Collection<E> tree(Collection<E> collection, Function<E, R> getId, Function<E, R> getParentId, BiConsumer<E, Collection<E>> setNode) {
Collection<E> root = new LinkedList<>();
for (E node : collection) {
R parentId = getParentId.apply(node);
R id = getId.apply(node);
Collection<E> elements = new LinkedList<>();
boolean isParent = true;
for (E element : collection) {
if (id.equals(getParentId.apply(element))) {
elements.add(element);
}
if (isParent && getId.apply(element).equals(parentId)) {
isParent = false;
}
}
if (isParent) {
root.add(node);
}
setNode.accept(node, elements);
}
return root;
}
/**
* 递归构造树
*
* @param allList 全部数据
* @param getId 子节点
* @param getParentId 父节点
* @param vertexId 顶点ID 可为空
* @param setNode 设置子集合
* @param <E> 集合的类型
* @param <R> 节点属性的类型
* @return List
*/
private static <E, R> List<E> constructionTree(List<E> allList, Function<E, R> getId, Function<E, R> getParentId, R vertexId, BiConsumer<E, List<E>> setNode) {
List<E> rootNodeList = new ArrayList<>();
List<E> treeList = new ArrayList<>();
allList.forEach(x -> {
if (Objects.equals(vertexId, getParentId.apply(x))) {
rootNodeList.add(x);
}
});
for (E treeRootNode : rootNodeList) {
buildChildTree(treeRootNode, allList, getId, getParentId, setNode);
treeList.add(treeRootNode);
}
return treeList;
}
/**
* 递归构造树(过滤)
*/
private static <E, R> List<E> constructionTree(List<E> list, Function<E, R> getId, Function<E, R> getParentId,
R vertexId, BiConsumer<E, List<E>> setNode, Function<E, E> filter) {
List<E> allList = new ArrayList<>();
List<E> rootNodeList = new ArrayList<>();
List<E> treeList = new ArrayList<>();
list.forEach(x -> {
E apply = filter.apply(x);
allList.add(apply);
if (Objects.equals(vertexId, getParentId.apply(x))) {
rootNodeList.add(apply);
}
});
for (E treeRootNode : rootNodeList) {
buildChildTree(treeRootNode, allList, getId, getParentId, setNode);
treeList.add(treeRootNode);
}
return treeList;
}
/**
* 递归-----构建子树形结构
*
* @param pNode 根节点(顶级节点)
* @param treeList 全部数据
* @return 整棵树
*/
public static <E, R> E buildChildTree(E pNode, List<E> treeList, Function<E, R> getId, Function<E, R> getParentId, BiConsumer<E, List<E>> setNode) {
List<E> childTree = new ArrayList<>();
for (E treeNode : treeList) {
if (getId.apply(pNode).equals(getParentId.apply(treeNode))) {
childTree.add(buildChildTree(treeNode, treeList, getId, getParentId, setNode));
}
}
setNode.accept(pNode, childTree);
return pNode;
}
/**
* 测试
*/
public static void main(String[] args) {
// 普通树1
List<MenuItem> menuList = Arrays.asList(
new MenuItem(1, "二级菜单1", 0),
new MenuItem(2, "二级菜单1", null),
new MenuItem(3, "二级菜单1", 2),
new MenuItem(4, "二级菜单2", 2),
new MenuItem(5, "三级菜单1", 3),
new MenuItem(6, "三级菜单2", 3)
);
// 普通树2
List<MenuItem> menuList2 = Arrays.asList(
new MenuItem(1, "二级菜单1", 0),
new MenuItem(2, "二级菜单1", null),
new MenuItem(3, "二级菜单1", 1),
new MenuItem(4, "二级菜单2", 2),
new MenuItem(5, "三级菜单1", 3),
new MenuItem(6, "三级菜单1", 3),
new MenuItem(7, "三级菜单2", 4)
);
// 过滤
List<DiyData> diyData = Arrays.asList(
new DiyData(1, "二级菜单1", 0),
new DiyData(2, "二级菜单1", null),
new DiyData(3, "二级菜单1", 1),
new DiyData(4, "二级菜单2", 2),
new DiyData(5, "三级菜单1", 3),
new DiyData(6, "三级菜单1", 3),
new DiyData(7, "三级菜单2", 4)
);
// 不过滤
List<DiyData> diyData2 = Arrays.asList(
new DiyData(1, "二级菜单1", 0),
new DiyData(2, "二级菜单1", null),
new DiyData(3, "二级菜单1", 1),
new DiyData(4, "二级菜单2", 2),
new DiyData(5, "三级菜单1", 3),
new DiyData(6, "三级菜单1", 3),
new DiyData(7, "三级菜单2", 4)
);
// 普通树1
Collection<MenuItem> tree = TreeUtils.tree(menuList, MenuItem::getId, MenuItem::getParentId, (x, y) -> x.setChildren((List<MenuItem>) y));
System.out.println(tree);
// 普通树2
List<MenuItem> tree2 = TreeUtils.constructionTree(menuList2, MenuItem::getId, MenuItem::getParentId, null, MenuItem::setChildren);
System.out.println(tree2);
// 过滤
List<DiyData> tree3 = TreeUtils.constructionTree(diyData, DiyData::getId, DiyData::getParentId, null, DiyData::setChildren
, x -> (DiyData) new DiyData()
.setKey(x.getId())
.setId(x.getId())
.setTitle(x.getName())
.setParentId(x.getParentId()));
System.out.println(tree3);
// 不过滤
List<DiyData> tree4 = TreeUtils.constructionTree(diyData2, DiyData::getId, DiyData::getParentId, null, DiyData::setChildren);
System.out.println(tree4);
}
/**
* 测试普通 实体类
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
static public class MenuItem {
private Integer id;
private String name;
private Integer parentId;
private List<MenuItem> children;
public MenuItem(Integer id, String name, Integer parentId) {
this.id = id;
this.name = name;
this.parentId = parentId;
}
}
/**
* 测试过滤 实体类
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
static public class DiyData extends TreeNode {
/**
* id 需映射 TreeNode 中的 key
*/
private Integer id;
/**
* id 需映射 TreeNode 中的 title
*/
private String name;
private Integer parentId;
}
/**
* 供前端组件使用,可自行修改
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public static class TreeNode {
/**
* 前端需要对应的id
*/
private Integer key;
/**
* 节点id
*/
private Integer id;
/**
* 父节点id
*/
private Integer parentId;
/**
* 前端需要对应公司的name
*/
private String title;
/**
* 子节点
*/
private List<? extends TreeNode> children;
}
}
测试
[TreeUtils.MenuItem(id=1, name=二级菜单1, parentId=0, children=[]), TreeUtils.MenuItem(id=2, name=二级菜单1, parentId=null, children=[TreeUtils.MenuItem(id=3, name=二级菜单1, parentId=2, children=[TreeUtils.MenuItem(id=5, name=三级菜单1, parentId=3, children=[]), TreeUtils.MenuItem(id=6, name=三级菜单2, parentId=3, children=[])]), TreeUtils.MenuItem(id=4, name=二级菜单2, parentId=2, children=[])])]
[TreeUtils.MenuItem(id=2, name=二级菜单1, parentId=null, children=[TreeUtils.MenuItem(id=4, name=二级菜单2, parentId=2, children=[TreeUtils.MenuItem(id=7, name=三级菜单2, parentId=4, children=[])])])]
[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=2, id=2, parentId=null, title=二级菜单1, children=[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=4, id=4, parentId=2, title=二级菜单2, children=[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=7, id=7, parentId=4, title=三级菜单2, children=[]), id=7, name=null, parentId=4)]), id=4, name=null, parentId=2)]), id=2, name=null, parentId=null)]
[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=null, id=2, parentId=null, title=null, children=[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=null, id=4, parentId=2, title=null, children=[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=null, id=7, parentId=4, title=null, children=[]), id=7, name=三级菜单2, parentId=4)]), id=4, name=二级菜单2, parentId=2)]), id=2, name=二级菜单1, parentId=null)]