前言
项目开发中经常会遇到树形结构,如多级菜单、多级文件夹结构、多级分类结构、多级组织结构,这些结构都有个共同特点,就是一般存在数据库中是通过id和parentId保存父子级关系的,返回给前端需要合成一颗树,本文针对这类数据结构,总结出常用合成树的三种方法。
方法一:递归合成法
一般在会有一个Menu对象
@Data
public class Menu {
public Menu(Integer id, Integer parentId, String name, Integer weight) {
this.id = id;
this.parentId = parentId;
this.name = name;
this.weight = weight;
}
private Integer id;
private Integer parentId;
private String name;
private Integer weight;
private List<Menu> children;
}
public static void main(String[] args) {
List<Menu> list = new ArrayList<>();
list.add(new Menu(1, 0, "用户管理", 2));
list.add(new Menu(2, 0, "租户管理", 1));
list.add(new Menu(3, 1, "添加用户", 3));
list.add(new Menu(4, 1, "删除用户", 2));
list.add(new Menu(5, 2, "添加租户", 1));
list.add(new Menu(6, 2, "删除租户", 2));
List<Menu> tree1 = buildTree(list);
System.out.println(JSONUtil.toJsonStr(tree1));
}
public static List<Menu> buildTree(List<Menu> list) {
return list.stream()
.filter(menu -> menu.getParentId() == 0)
.peek(menu -> menu.setChildren(getChildrens(menu, list)))
.sorted(Comparator.comparing(Menu::getWeight))
.collect(Collectors.toList());
}
public static List<Menu> getChildrens(Menu root, List<Menu> allMenus) {
return allMenus.stream()
.filter(menu -> Objects.equals(menu.getParentId(), root.getId()))
.peek(menu -> menu.setChildren(getChildrens(menu, allMenus)))
.sorted(Comparator.comparing(Menu::getWeight))
.collect(Collectors.toList());
}
方法二:使用泛型通用合成法
public interface TreeNode<T, E> extends Comparable<E> {
T getId();
T getParentId();
T getWeight();
boolean isRoot();
void setChildren(List<? extends TreeNode<T, E>> children);
}
@Data
public class Menu implements TreeNode<Integer, Menu> {
public Menu(Integer id, Integer parentId, String name, Integer weight) {
this.id = id;
this.parentId = parentId;
this.name = name;
this.weight = weight;
}
private Integer id;
private Integer parentId;
private String name;
private Integer weight;
private List<Menu> children;
@Override
public void setChildren(List menus) {
this.children = menus;
}
@Override
public boolean isRoot() {
return this.parentId == 0;
}
@Override
public int compareTo(Menu o) {
return this.weight.compareTo(o.getWeight());
}
}
public static void main(String[] args) {
List<Menu> list = new ArrayList<>();
list.add(new Menu(1, 0, "用户管理", 2));
list.add(new Menu(2, 0, "租户管理", 1));
list.add(new Menu(3, 1, "添加用户", 3));
list.add(new Menu(4, 1, "删除用户", 2));
list.add(new Menu(5, 2, "添加租户", 1));
list.add(new Menu(6, 2, "删除租户", 2));
List<Menu> tree2 = tree(list);
System.out.println(JSONUtil.toJsonStr(tree2));
}
public static <E extends TreeNode> List<E> getSubs(E parent, List<E> allData) {
return allData.stream()
.filter(x -> Objects.equals(x.getParentId(), parent.getId()))
.peek(x -> x.setChildren(getSubs(x, allData)))
.sorted()
.collect(Collectors.toList());
}
public static <E extends TreeNode> List<E> tree(List<E> allData) {
return allData.stream()
.filter(TreeNode::isRoot)
.peek(x -> x.setChildren(getSubs(x, allData)))
.sorted()
.collect(Collectors.toList());
}
方法三:使用Hutool 工具TreeUtill合成法
public static void main(String[] args) {
List<Menu> list = new ArrayList<>();
list.add(new Menu(1, 0, "用户管理", 2));
list.add(new Menu(2, 0, "租户管理", 1));
list.add(new Menu(3, 1, "添加用户", 3));
list.add(new Menu(4, 1, "删除用户", 2));
list.add(new Menu(5, 2, "添加租户", 1));
list.add(new Menu(6, 2, "删除租户", 2));
TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
treeNodeConfig.setIdKey("id");
List<Tree<Integer>> tree3 = TreeUtil.build(list, 0, treeNodeConfig, (node, tree) -> {
tree.setId(node.getId());
tree.setParentId(node.getParentId());
tree.setName(node.getName());
tree.setWeight(node.getWeight());
});
System.out.println(JSONUtil.toJsonStr(tree3));
}
方法四:使用Lambda 表达式改写泛型接口
public static void main(String[] args) {
List<Menu> list = new ArrayList<>();
list.add(new Menu(1, 0, "用户管理", 2));
list.add(new Menu(2, 0, "租户管理", 1));
list.add(new Menu(3, 1, "添加用户", 3));
list.add(new Menu(4, 1, "删除用户", 2));
list.add(new Menu(5, 2, "添加租户", 1));
list.add(new Menu(6, 2, "删除租户", 2));
List<Menu> menus= makeTree(list,x-> x.getParentId()==0,(x,y)-> x.getId().equals(y.getParentId()), Menu::setChildren);
System.out.println(JSONUtil.toJsonStr(menus));
}
public static <E> List<E> makeTree(List<E> list, Predicate<E> root, BiFunction<E,E,Boolean> parentCheck, BiConsumer<E,List<E>> children) {
return list.stream().filter(root).peek(x->children.accept(x,makeChildren(x,list, parentCheck,children))).collect(Collectors.toList());
}
public static <E> List<E> makeChildren(E parent, List<E> allData, BiFunction<E,E,Boolean> parentCheck, BiConsumer<E,List<E>> children) {
return allData.stream().filter(x-> parentCheck.apply(parent,x)).peek(x->children.accept(x,makeChildren(x,allData, parentCheck,children))).collect(Collectors.toList());
}
总结
-
方法一:
优点:逻辑简单清晰
缺点:代码不能复用 -
方法二:
优点:代码可以复用
缺点:需要实现接口,代码侵入性太强 -
方法三:
优点:无代码侵入,可复用
缺点:需要引用Hutool包,返回对象变成Hutoo的Tree -
方法四:
优点:无代码侵入,代码可复用,不需要引用额外包
缺点:不支持JDK1.8以下版本
版权声明:本文为whzhaochao原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。