java - 组装树形结构
基本原理:将业务上的对象,转换成 TreeNode 对象,然后通过 TreeBuilder 对象,将数据组装成树结构。
顾虑
不少程序员,会排斥这种设计,我们的 TreeNode 能兼容所有设计嘛?
显然不能,包括我自己,曾经也是反对这种设计的。
但是,如果不形成标准,很多更好的idea,都无法实施。
比如:前端程序员,想设计一个配套的树形组件,如果你连关键字名称用 key 还是 id 都没确定,那事情都无法继续了。
树节点
import cn.seaboot.commons.core.CommonUtils;
import java.io.Serializable;
import java.util.List;
/**
* 标准树节点
* <p>
* 命名方式较为大众化,能直接对接各类组件
* <p>
* 注意:不规范的树形结构,子节点指向父节点,打印的时候会形成无限递归
*
* @author Mr.css
* @version 2024-04-26 10:50
*/
public class TreeNode<T> implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8373046449086958894L;
/**
* ID 关键字
*/
private String id;
/**
* 父级 ID 关键字
*/
private String pid;
/**
* 标签
*/
private String label;
/**
* 图标
*/
private String icon;
/**
* 是否选中
*/
private Boolean checked;
/**
* 数据实体
*/
private T data;
/**
* 父节点
*/
private TreeNode<T> parent;
/**
* 子节点
*/
private List<TreeNode<T>> children;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public TreeNode<T> getParent() {
return parent;
}
public void setParent(TreeNode<T> parent) {
this.parent = parent;
}
public List<TreeNode<T>> getChildren() {
return children;
}
public void setChildren(List<TreeNode<T>> children) {
this.children = children;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public Boolean getChecked() {
return checked;
}
public void setChecked(Boolean checked) {
this.checked = checked;
}
/**
* 是否有父级
*
* @return if true
*/
public boolean hasParent() {
return this.parent != null;
}
/**
* 是否有子节点
*
* @return if true
*/
public boolean hasChildren() {
return CommonUtils.isNotEmpty(children);
}
/**
* 注意:如果子节点指向父级,会形成无限递归,此函数会造成堆栈溢出
*
* @return -
*/
@Override
public String toString() {
return "TreeNode{" +
"id='" + id + '\'' +
", pid='" + pid + '\'' +
", label='" + label + '\'' +
", icon='" + icon + '\'' +
", checked='" + checked + '\'' +
", data=" + data +
", parent=" + parent +
", children=" + children +
'}';
}
}
配套树形结构拼装工具
package cn.seaboot.commons.lang;
import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.exception.BizException;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* E.G.:
* List<Map<String,Object>> tree = new TreeBuilder()
* .setParent(list)
* .setChild(list)
* .groupBy()
* .getByMinPid();
* <p>
* 基于 Java8 封装的树形结构组装工具。
* <p>
* 其中,如果父节点如果为 null,通常属于根节点,算法上也是这么判断的。
*
* @author Mr.css
* @version 2020-07-06 11:18
*/
public class TreeBuilder<T> {
/**
* 父级
*/
private List<TreeNode<T>> parent;
/**
* 子级
*/
private List<TreeNode<T>> child;
/**
* 数据映射
*/
private Function<T, TreeNode<T>> mapper;
/**
* 数据映射函数,将数据转换成 TreeNode
*
* @param mapper 数据映射函数
* @return this
*/
public TreeBuilder<T> setMapper(Function<T, TreeNode<T>> mapper) {
this.mapper = mapper;
return this;
}
/**
* 父节点列表
*
* @param p parent-list
* @return this
*/
public TreeBuilder<T> setParent(List<TreeNode<T>> p) {
this.parent = p;
return this;
}
/**
* 子节点列表
*
* @param child child-list
* @return this
*/
public TreeBuilder<T> setChild(List<TreeNode<T>> child) {
this.child = child;
return this;
}
/**
* 映射父节点列表
*
* @param p parent-list
* @return this
*/
public TreeBuilder<T> mapParent(List<T> p) {
this.parent = p.stream().map(mapper).collect(Collectors.toList());
return this;
}
/**
* 映射成子节点列表
*
* @param child child-list
* @return this
*/
public TreeBuilder<T> mapChild(List<T> child) {
this.child = child.stream().map(mapper).collect(Collectors.toList());
return this;
}
public List<TreeNode<T>> getParent() {
return parent;
}
public List<TreeNode<T>> getChild() {
return child;
}
public Function<T, TreeNode<T>> getMapper() {
return mapper;
}
/**
* 数据分组
*
* @return TreeBuilder
*/
public TreeBuilder<T> groupBy() {
if (CommonUtils.isEmpty(parent) && CommonUtils.isEmpty(child)) {
throw new IllegalArgumentException("Child list and parent list are both empty!");
}
if (CommonUtils.isEmpty(parent)) {
this.parent = this.child;
}
if (CommonUtils.isEmpty(child)) {
this.child = this.parent;
}
for (TreeNode<T> p : parent) {
for (TreeNode<T> c : child) {
// 此处可能发生空指针,注意初始化集合
if (p.getId().equals(c.getPid())) {
// 子节点指向父节点
c.setParent(p);
// 父节点指向子节点
List<TreeNode<T>> list = p.getChildren();
if (CommonUtils.isEmpty(list)) {
list = new ArrayList<>();
p.setChildren(list);
}
list.add(c);
}
}
}
return this;
}
/**
* 如果已知要取的节点,直接按照节点pId进行获取
*
* @param pid 值可能为空,数据库可能用null值表示顶级节点
* @return tree
*/
public List<TreeNode<T>> getByPid(@Nullable Object pid) {
List<TreeNode<T>> res = new ArrayList<>();
if (pid == null) {
for (TreeNode<T> p : parent) {
if (p.getPid() == null) {
res.add(p);
}
}
} else {
for (TreeNode<T> p : parent) {
if (pid.equals(p.getPid())) {
res.add(p);
}
}
}
return res;
}
/**
* 从结果集中取出 pid 最小的数据
* <p>
* 特例:如果存在 pid 为 null 的数据,则将该数据,作为最小的数据
*
* @return tree
*/
public List<TreeNode<T>> getByMinPid() {
String min = parent.get(0).getPid();
String pid;
for (TreeNode<T> p : parent) {
pid = p.getPid();
if (pid == null) {
return this.getByPid(null);
} else {
if (pid.compareTo(min) < 0) {
min = pid;
}
}
}
return this.getByPid(min);
}
/**
* 从组装的结果中取出Pid最大的数据
* <p>
* 特例:如果存在 pid 为 null 的数据,则将该数据,作为最大的数据
*
* @return tree
*/
public List<TreeNode<T>> getByMaxPid() {
String max = parent.get(0).getPid();
String pid;
for (TreeNode<T> p : parent) {
pid = p.getPid();
if (pid == null) {
return this.getByPid(null);
} else {
if (pid.compareTo(max) > 0) {
max = pid;
}
}
}
return this.getByPid(max);
}
/**
* 随机取出一个元素,反复遍历树,直到取出最顶级的父节点。
* <p>
* 树状结构越深,需要遍历的次数越多。
*
* @return tree
*/
public List<TreeNode<T>> getRoot() {
// 以第一个节点的 pid 作为顶级节点,不断递归检索
TreeNode<T> top = parent.get(0);
// 声明一个 list 记录已经查找过的节点,避免出现无限递归
List<String> list = new ArrayList<>();
list.add(top.getPid());
while (top.hasParent()) {
top = top.getParent();
if (list.contains(top.getPid())) {
// 出现无限递归的数据,异常退出
throw new BizException("Circular dependencies:" + top);
} else {
// 继续下一次递归检索
list.add(top.getPid());
}
}
return this.getByPid(top.getPid());
}
}
疯狂的妞妞 :每一天,做什么都好,不要什么都不做!