1. Define a generic interface
// 实现接口需要传入两个参数,第一个T 为节点中的id,第二个R 为节点,TreeNode。
public interface TreeNode<T, R extends TreeNode<T, R>> {
/**
* 获取节点id
*
* @return 树节点id
*/
T id();
/**
* 获取该节点的父节点id
*
* @return 父节点id
*/
T parentId();
/**
* 是否是根节点
*
* @return true:根节点
*/
boolean isRoot();
/**
* 设置节点的子节点列表
*
* @param children 子节点
*/
void setChildren(List<R> children);
/**
* 获取所有子节点
*
* @return 子节点列表
*/
List<R> getChildren();
/**
* 是否叶子节点
*
* @return 是否叶子节点
*/
default boolean isLeaf() {
return getChildren().size() == 0;
}
/**
* 列表生成树结构
*
* @param nodes 要生成树状结构的集合
* @param <T> 键类型
* @param <R> 结点类型
* @return 树结构
*/
static <T, R extends TreeNode<T, R>> List<R> generateTree(List<R> nodes) {
List<R> result = new ArrayList<>();
// 从性能考虑。(nodes.size() / .75f) + 1 是HashMap总的空间大小
Map<T, R> id2Node = new LinkedHashMap<>(Math.max((int) (nodes.size() / .75f) + 1, 16));
nodes.forEach(e -> {
id2Node.put(e.id(), e);
e.setChildren(new ArrayList<>());
});
id2Node.forEach((id, node) -> {
if (!node.isRoot()) {
// 若非根节点
R parent = id2Node.get(node.parentId()); // 拿到父节点
if (parent != null) {
List<R> children = parent.getChildren(); // 拿到父节点的所有子节点
if (children == null) {
// 子节点为null就初始化
children = new ArrayList<>();
parent.setChildren(children);
}
children.add(node); // 将当前节点加到父亲的子节点集合
}
} else {
// 根节点
result.add(node);
}
});
return result; // 返回根节点
}
}
2. Implement the generic interface
My requirement here is to return all administrative districts in the form of a tree. For example, province --> city --> district
So define the administrative tree structure:
@Data
@ApiModel(value = "行政区树结构VO")
// 这里必须实现 TreeNode<T, R> 其中R类型必须继承或实现了 TreeNode
public class DistrictTreeVO implements TreeNode<String, DistrictTreeVO> {
@ApiModelProperty("行政区编码")
private String districtCode;
@ApiModelProperty("行政区名称")
private String districtName;
@ApiModelProperty("父行政区编码")
private String parentDistrictCode;
@ApiModelProperty("子级行政区")
private List<DistrictTreeVO> children;
// 实现泛型接口中未实现的方法。获取id,获取pid,判断是否是根节点
// 泛型接口中已经实现的方法可以直接调用
@Override
public String id() {
return this.getDistrictCode();
}
@Override
public String parentId() {
return this.getParentDistrictCode();
}
@Override
public boolean isRoot() {
return StringUtils.equals(this.getParentDistrictCode(), TradeXzq.MAX_PARENT_DISTRICT_CODE);
}
}
3. Query test
DistrictTreeVO is R and String is T when the TreeNode.generateTree method is called. According to the implementation in the class DistrictTreeVO shall prevail.
4. To return to the paging tree structure
For example the following form:
The original idea is to recursively search for its child nodes for each first-level node after obtaining the first-level nodes according to the pagination, and set the value. But it's very inefficient, so consider the following approach!
The solution is: add a field path to the table. Used to represent the tree path identifier.
The path value of the first-level node is the id of the first-level node
The path value of the non-first-level node is the path value of the first-level node above it
(1) Query all first-level nodes according to the pagination conditions
(2) Query all nodes under these first-level nodes according to the Id of the queried first-level nodes (realized by adding a new field path, for the same tree All nodes of the path have the same path value)
(3) According to all the nodes obtained from these, just build a tree!
public PageResult<DeviceCategoryTreeRespVO> getDeviceCategoryPage(DeviceCategoryPageReqVO pageReqVO) {
// 拿到一级节点
PageResult<DeviceCategoryTreeRespVO> result = DeviceCategoryConvert.INSTANCE.convertTreePage(deviceCategoryMapper.selectTreePage(pageReqVO));
// 通过 in 语句拿到所有一级节点下的节点(包括一级节点)只要path值为这几个一级节点的id之一,就说明是这几个一级节点下的点!
List<DeviceCategoryTreeRespVO> childList = DeviceCategoryConvert.INSTANCE.convertTreeList(
deviceCategoryMapper.selectList(Wrappers.lambdaQuery(DeviceCategoryDO.class)
.in(ObjectUtil.isNotNull(result.getList()), DeviceCategoryDO::getPath, result.getList().stream().map(po -> po.getId()).collect(Collectors.toList()))
.eq(DeviceCategoryDO::getShowStatus, "1"))
);
// 通过获取的这些节点 建树!
result.setList(TreeNode.generateTree(childList));
return result;
}