引言
在软件开发的世界中,树状结构是一个常见的数据形式,用于表示各种层次化的信息,如组织结构、文件夹结构和分类系统。但是,要将扁平的数据列表转换为树状结构可能会让人头疼。给大家分享一个工具类,仅需要三个注解,即可生成标准的树状结构数据。
树状结构是什么
树状结构列表是一种常见的数据结构,它模拟了树的形状,由节点组成,具有以下特点:
- 根节点(Root):树的顶部节点,是树的起始点,没有父节点。
- 节点(Node):树中的元素,包含数据以及指向子节点的引用。
- 父节点(Parent Node):每个节点都有一个父节点,除了根节点。
- 子节点(Child Node):节点下面的直接子元素,可以有多个子节点。
树状结构在编程中的应用场景
- 文件系统:计算机的文件和文件夹组织通常以树状结构表示,其中根节点是根目录,子节点是文件和文件夹。
- 组织架构:公司、学校等组织的层次结构可以用树来表示,每个节点代表一个部门或单位。
- 图形界面控件:GUI应用程序中,用户界面元素(如按钮、文本框)通常以树的形式层次化排列。
- 分类和标签:在博客、商品等应用中,可以使用树状结构对内容进行分类和标签化
尽管树状结构在编程中有广泛的应用,但将扁平化的数据转换为树状结构可能是一项复杂和繁琐的任务,涉及到递归、节点关系的建立等操作。因此,有时需要一个专门的工具类来简化这个过程
工具类介绍
工具名称:TreeListUtil
用途: TreeListUtil工具类用于处理树状结构的数据,这种数据通常包含父子关系,如组织结构、分类体系、评论回复等。
主要功能:
- 构建树状结构: TreeListUtil工具类可以根据指定的数据列表,将数据构建成树状结构。通常,这个数据列表包含了各个节点的信息,例如ID、父节点ID和子节点列表。
- 优雅的API: 该工具类提供了简洁的API,使得构建树状结构变得非常容易。你只需提供数据列表,工具类会自动帮你构建出树状结构。
- 注解支持: 工具类支持使用注解来标识数据对象中的关键字段,如ID、父节点ID和子节点列表字段。这使得工具类可以适用于不同的数据对象。
- 异常处理: 工具类具备良好的异常处理机制,以确保在使用过程中出现问题时能够提供有用的错误信息,帮助你诊断和解决问题。
- 类型安全性: 工具类在处理数据对象时,会进行类型安全性检查,以确保字段的类型与预期一致。
工具代码
TreeCruxEnum.java
public enum TreeCruxEnum {
ID,
PID,
CHILD_LIST
}
TreeCruxField.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TreeCruxField {
TreeCruxEnum value();
}
TreeListUtil.java
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class TreeListUtil<T> {
private final Field idField; // ID字段
private final Field parentIdField; // 父节点ID字段
private final Field childrenField; // 子节点列表字段
/**
* 构造函数,初始化TreeListUtil对象。
*
* @param classType 要操作的数据类的Class对象。
*/
public TreeListUtil(Class<T> classType) {
// 获取并设置ID字段、父节点ID字段和子节点列表字段
this.idField = getFieldWithTreeCruxAnnotation(classType, TreeCruxEnum.ID);
this.parentIdField = getFieldWithTreeCruxAnnotation(classType, TreeCruxEnum.PID);
this.childrenField = getFieldWithTreeCruxAnnotation(classType, TreeCruxEnum.CHILD_LIST);
}
/**
* 根据注解值获取带有TreeCruxField注解的字段。
*
* @param clazz 数据类的Class对象。
* @param annotationValue 注解值(ID、父节点ID或子节点列表)。
* @return 带有指定注解的字段。
*/
private Field getFieldWithTreeCruxAnnotation(Class<T> clazz, TreeCruxEnum annotationValue) {
List<Field> annotatedFields = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
TreeCruxField annotation = field.getAnnotation(TreeCruxField.class);
if (annotation != null && annotation.value() == annotationValue) {
annotatedFields.add(field);
}
}
Assert.isTrue(annotatedFields.size() == 1,
String.format("class %s TreeCruxField value is more than one attribute for %s ", clazz.getName(), annotationValue));
Field field = annotatedFields.get(0);
field.setAccessible(true); // 设置字段访问权限为可访问
return field;
}
/**
* 构建树状结构。
*
* @param dataList 包含数据的列表。
* @return 树状结构的根节点列表。
*/
public List<T> buildTree(List<T> dataList) {
List<T> treeList = new ArrayList<>();
dataList.stream()
.filter(obj -> {
try {
// 过滤出根节点
return 0 == (Long) parentIdField.get(obj);
} catch (IllegalAccessException e) {
throw new RuntimeException("The parent ID field is not of type Long", e);
}
})
.peek(treeList::add)
.forEach(root -> addChildren(root, dataList));
return treeList;
}
/**
* 递归添加子节点。
*
* @param parent 父节点。
* @param dataList 包含数据的列表。
*/
private void addChildren(T parent, List<T> dataList) {
dataList.stream()
.filter(data -> {
try {
// 查找与父节点ID匹配的子节点
return parentIdField.get(data).equals(idField.get(parent));
} catch (IllegalAccessException e) {
throw new RuntimeException("The ID and parent ID field types are different", e);
}
})
.peek(child -> {
try {
// 添加子节点到父节点的子节点列表
List<T> childList = (List<T>) childrenField.get(parent);
if (childList == null) {
childList = new ArrayList<>();
childrenField.set(parent, childList);
}
childList.add(child);
} catch (IllegalAccessException e) {
throw new RuntimeException("Error accessing child list field", e);
}
})
.forEach(child -> addChildren(child, dataList));
}
}
工具类的使用方式
Entity定义
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class User {
@TreeCruxField(TreeCruxEnum.ID)
private Long id; // 用来定义id字段
@TreeCruxField(TreeCruxEnum.PID)
private Long pid; // 用来记录父节点id
private String name;
@TreeCruxField(TreeCruxEnum.CHILD_LIST)
private List<User> sonList; // 用来保存子集合
public User(Long id, Long pid, String name) {
this.id = id;
this.pid = pid;
this.name = name;
this.sonList = new ArrayList<>();
}
}
MyTest.java
import java.util.ArrayList;
import java.util.List;
public class MyTest {
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User(1L, 0L, "电影"));
userList.add(new User(1009L, 3L, "亮剑电影"));
userList.add(new User(2L, 0L, "书"));
userList.add(new User(3L, 1L, "战争片"));
userList.add(new User(4L, 1L,"武打片"));
userList.add(new User(5L, 1L, "爱情片"));
userList.add(new User(6L, 2L,"战争书"));
userList.add(new User(193L, 1009L, "亮剑儿子"));
userList.add(new User(8L, 6L,"亮剑"));
TreeListUtil<User> treeListUtil = new TreeListUtil(User.class);
List<User> objects = treeListUtil.buildTree(userList);
for (User user : objects) {
printTree(user," ");
}
}
public static void printTree(User user, String prefix) {
System.out.println(prefix + user.getName());
for (User child : user.getSonList()) {
printTree(child, prefix + "-");
}
}
}
你可以根据具体的需求和数据对象进行配置和使用。TreeListUtil工具类可以让你轻松地处理树状结构的数据,提高了代码的可读性和可维护性。
运行结果
电影
-战争片
--亮剑电影
---亮剑儿子
-武打片
-爱情片
书
-战争书
--亮剑
TreeListUtil工具类为处理树状结构数据提供了支持,它的简洁API、注解支持和异常处理机制使其成为项目开发中的宝贵工具。无论是处理组织结构、评论回复、分类体系还是其他树状数据,TreeListUtil都能够轻松应对。我们鼓励你在项目中尝试使用TreeListUtil,体验其带来的便利和效率提升。如果你有任何问题或建议,欢迎留言或与我联系。愿TreeListUtil工具类能够在你的项目中发挥作用,让树状结构数据处理变得更加简单和高效。