详细分析Java的树形工具类(含注释)

目录

  • 前言
  • 1. 基本框架
  • 2. 实战应用

前言

对应的每个子孙属于该父亲,这其实是数据结构的基础知识,那怎么划分怎么归属呢

对应的基本知识推荐如下:

  1. 【数据结构】树和二叉树详细分析(全)
  2. 【数据结构】B树和B+树的笔记详细诠释

1. 基本框架

最基本的树形结构节点,一个自身ID,一个父亲ID,一个孩子ID:

import java.io.Serializable;
import java.util.List;

public interface INode<T> extends Serializable {
    Long getId();

    Long getParentId();

    List<T> getChildren();

    default Boolean getHasChildren() {
        return false;
    }
}

其中ForestNodeManager 树形管理类主要表示如下:

  • nodeMap 存储节点,并提供了一些方法来操作节点和管理树状结构
  • getRoot 方法用于获取合并后的树的根节点列表
  • addParentId 方法用于标记尚未创建的父节点
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ForestNodeManager<T extends INode<T>> {
	/**
	 * 使用 ImmutableMap 存储节点,其中键是节点的ID,值是节点本身。
	 * 这个映射是不可变的,通过调用 Maps.uniqueIndex(nodes, INode::getId) 来创建,其中 INode::getId 是获取节点ID的方法。
	 * 
	 * parentIdMap: 使用 HashMap 存储尚未创建的父节点的ID。键是父节点的ID,值是一个占位对象(在这里是空字符串)。
	 */
    private final ImmutableMap<Long, T> nodeMap;
    private final Map<Long, Object> parentIdMap = Maps.newHashMap();
	
	/**
	 * 接受一个 List 类型的参数 nodes,并使用 Maps.uniqueIndex 方法将其转换为 ImmutableMap 存储在 nodeMap 中
	 */
    public ForestNodeManager(List<T> nodes) {
        this.nodeMap = Maps.uniqueIndex(nodes, INode::getId);
    }
	
	/**
	 * 接受一个 Long 类型的节点ID作为参数,然后尝试从 nodeMap 中获取对应ID的节点。
	 * 如果存在该节点,则返回节点;否则返回 null。
	 */
    public INode<T> getTreeNodeAt(Long id) {
        return this.nodeMap.containsKey(id) ? (INode)this.nodeMap.get(id) : null;
    }
	
	/**
	 * 接受一个 Long 类型的父节点ID作为参数,将其添加到 parentIdMap 中。
	 * 这个方法用于标记尚未创建的父节点。
	 */
    public void addParentId(Long parentId) {
        this.parentIdMap.put(parentId, "");
    }
	
	/**
	 * 创建一个空的 ArrayList 用于存储树的根节点。
	 * 使用 forEach 遍历 nodeMap 中的每个节点。
	 * 对于每个节点,如果其父节点ID为0(表示是根节点)或者父节点ID在 parentIdMap 中存在(即尚未创建的父节点),则将该节点添加到根节点列表中。
	 * 最后返回根节点列表。
	 */
    public List<T> getRoot() {
        List<T> roots = new ArrayList();
        this.nodeMap.forEach((key, node) -> {
            if (node.getParentId() == 0L || this.parentIdMap.containsKey(node.getId())) {
                roots.add(node);
            }

        });
        return roots;
    }
}

对于ForestNodeMerger合并类:

包含树状结构节点的列表,根据节点之间的父子关系进行合并,最终返回合并后的树的根节点列表。

import java.util.List;

public class ForestNodeMerger {
    public ForestNodeMerger() {
    }
	
	/**
	 * 对于每个节点,通过 getParentId() 方法获取其父节点的ID。
	 * 如果父节点ID不等于0(即有父节点),则尝试通过 forestNodeManager.getTreeNodeAt() 方法获取父节点。
	 * 如果成功获取到父节点,则将当前节点添加到父节点的子节点列表中。否则,说明父节点尚未被创建,需要通过 forestNodeManager.addParentId() 方法添加到待创建父节点的列表中。
	 * 
	 * 最后,通过 forestNodeManager.getRoot() 方法获取合并后的树的根节点列表,并返回这个列表。
	 */
    public static <T extends INode<T>> List<T> merge(List<T> items) {
        ForestNodeManager<T> forestNodeManager = new ForestNodeManager(items);
        items.forEach((forestNode) -> {
            if (forestNode.getParentId() != 0L) {
                INode<T> node = forestNodeManager.getTreeNodeAt(forestNode.getParentId());
                if (node != null) {
                    node.getChildren().add(forestNode);
                } else {
                    forestNodeManager.addParentId(forestNode.getId());
                }
            }

        });
        return forestNodeManager.getRoot();
    }
}

2. 实战应用

大多数本身Mapper 或者 Service基层都会帮我们实现增删改查,主要是XML文件对数据库的逻辑

本身就是MybatisPlus框架,推荐阅读:

  1. Springboot整合MybatisPlus的基本CRUD(全)
  2. MyBatis-plus从入门到精通(全)

与日常开发差不多,主要是上述中的方法如何套用!


假设要做一个菜单专栏,必须由这种树形结构来管理,好方便那个子类归属那个父类!

在这里插入图片描述
对应的类别如下:

@Data
@TableName("menu")
@ApiModel(value = "Menu对象", description = "Menu对象")
public class Menu implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
	 * 主键
	 */
	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty(value = "主键")
	@TableId(value = "id", type = IdType.ASSIGN_ID)
	private Long id;

	/**
	 * 菜单父主键
	 */
	@JsonSerialize(using = ToStringSerializer.class)
	@ApiModelProperty(value = "菜单父主键")
	private Long parentId;

	/**
	 * 菜单编号
	 */
	@ApiModelProperty(value = "菜单编号")
	private String code;

	/**
	 * 菜单名称
	 */
	@ApiModelProperty(value = "菜单名称")
	private String name;

	/**
	 * 菜单别名
	 */
	@ApiModelProperty(value = "菜单别名")
	private String alias;

	/**
	 * 请求地址
	 */
	@ApiModelProperty(value = "请求地址")
	private String path;

	/**
	 * 菜单资源
	 */
	@ApiModelProperty(value = "菜单资源")
	private String source;

	/**
	 * 排序
	 */
	@ApiModelProperty(value = "排序")
	private Integer sort;

	/**
	 * 菜单类型
	 */
	@ApiModelProperty(value = "菜单类型")
	private Integer category;

	/**
	 * 操作按钮类型
	 */
	@ApiModelProperty(value = "操作按钮类型")
	private Integer action;

	/**
	 * 是否打开新页面
	 */
	@ApiModelProperty(value = "是否打开新页面")
	private Integer isOpen;

	/**
	 * 备注
	 */
	@ApiModelProperty(value = "备注")
	private String remark;

	/**
	 * 是否已删除
	 */
	@TableLogic
	@ApiModelProperty(value = "是否已删除")
	private Integer isDeleted;


	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		Menu other = (Menu) obj;
		if (Func.equals(this.getId(), other.getId())) {
			return true;
		}
		return false;
	}

}

对应与前端交互的VO类如下:public class MenuVO extends Menu implements INode,大部分这里都是这种写法!

@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "MenuVO对象", description = "MenuVO对象")
public class MenuVO extends Menu implements INode<MenuVO> {
	private static final long serialVersionUID = 1L;

	/**
	 * 主键ID
	 */
	@JsonSerialize(using = ToStringSerializer.class)
	private Long id;

	/**
	 * 父节点ID
	 */
	@JsonSerialize(using = ToStringSerializer.class)
	private Long parentId;

	/**
	 * 子孙节点
	 */
	@JsonInclude(JsonInclude.Include.NON_EMPTY)
	private List<MenuVO> children;
	/**
	 * 是否有子孙节点
	 */
	@JsonInclude(JsonInclude.Include.NON_EMPTY)
	private Boolean hasChildren;

	@Override
	public List<MenuVO> getChildren() {
		if (this.children == null) {
			this.children = new ArrayList<>();
		}
		return this.children;
	}

	/**
	 * 上级菜单
	 */
	private String parentName;

	/**
	 * 菜单类型
	 */
	private String categoryName;

	/**
	 * 按钮功能
	 */
	private String actionName;

	/**
	 * 是否新窗口打开
	 */
	private String isOpenName;
}

对应的懒加载实现类主要如下:

	@Override
	public List<MenuVO> lazyList(Long parentId, Map<String, Object> param) {
		if (Func.isEmpty(Func.toStr(param.get("parentId")))) {
			parentId = null;
		}
		return baseMapper.lazyList(parentId, param);
	}

对应获取树形结构的实现类如下:

	@Override
	public List<MenuVO> tree() {
		return ForestNodeMerger.merge(baseMapper.tree());
	}

其中涉及Mapper的方法主要在xml文件中实现:

<select id="tree" resultMap="treeNodeResultMap">
        select id, parent_id, name as title, id as "value", id as "key" from menu where is_deleted = 0 and category = 1
select>

你可能感兴趣的:(java,java,树形工具类)