icexmoon-tree
一個輕量級的 Java 工具庫,提供樹形結構操作功能。
安裝
<dependency><groupId>cn.icexmoon</groupId><artifactId>icexmoon-tree</artifactId><version>1.0.0</version>
</dependency>
使用
構建樹
假設需要用樹構建的數據類型如下:
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString
public class Department implements Nodeable<Department> {@EqualsAndHashCode.Includeprivate Long id;private String name;private Long parentId;private Department parent;private List<Department> children;public Department(Long id, String name, Long parentId) {this.id = id;this.name = name;this.parentId = parentId;}
}
這里實現 Nodeable 接口并非必須,getter\setter\equals\hashcode 方法的實現也不是必須。
假設用 Department 構建了一些有樹層級關系的測試數據:
public static final List<Department> departments = List.of(new Department(1L, "宇宙科技有限公司", null),new Department(2L, "人事部", 1L),new Department(3L, "系統開發部", 1L),new Department(4L, "開發一部", 3L),new Department(5L, "開發二部", 3L),new Department(6L, "OA項目組", 4L));
這里 Department 實例的父子關系由 parentId 屬性指定,當然這也不是必須的,樹構建時關聯關系依賴于用戶提供的匿名函數,只要用戶將怎么關聯父子信息通過匿名函數告訴樹即可。
通過構造函數創建樹實例:
public static final Tree<Department> tree = new Tree<>(dept -> {Long id = dept.getId();List<Department> children = new ArrayList<>();for (Department department : departments) {if (department.getParentId() != null && department.getParentId().equals(id)) {children.add(department);}}return children;
}, departments::getFirst);
Tree
的構造器需要提供兩個參數,都是匿名函數:
- 匿名函數 GetDirectChildren:提供一個要包含在樹節點內的值(Value),返回其所有的子值(Child Value)。
- 匿名函數 GetRoot:返回根節點的值。
銷毀樹
樹的節點信息是保存在內存中的(Tree 對象的 root 字段),因此如果樹創建時依賴的數據的父子關系發生變化,比如 department 表中新增了一個子部門或者刪除了一個子部門。需要調用 API 銷毀內存中的樹結構,以便再次訪問樹時能重新構建樹的父子關系。如果不這么做就可能導致一些 BUG。
銷毀樹:
tree.destroy();
遍歷樹
遍歷樹是一個常見操作,本項目支持通過一個匿名函數 Consumer 在遍歷樹時對節點執行一些操作:
tree.traversal(node -> {List<Node<Department>> allParents = tree.getAllParents(node);List<String> names = allParents.stream().map(n -> n.getValue().getName()).toList();String fullNames = String.join("/", names);System.out.println(fullNames);
});
getAllParents
方法會返回指定節點的所有父節點,具體可以參考源碼。- 默認采用深度優先遍歷,提供其他 API 可以指定廣度遍歷或深度遍歷。
查找節點
有時候需要根據指定條件查找樹上的某個節點,可以:
Node<Department> findNode = tree.findNode(node -> node.getChildren() != null && node.getValue().getId() == 5);
System.out.println(findNode.getValue());
就像示例展示的,findNode
方法接收一個 Predicate 類型的匿名函數作為匹配條件。
簡單樹拷貝
做前后端分離開發的時候,前端獲取到的樹不希望是類似:
{value: {id: 1,name: "宇宙科技有限公司",parentId: 0,//...},children[{value: {id: 2,name: "人事部",parentId: 1,//...},children:[...]}]
}
當然,直接使用是沒有任何問題的,但如果前端一定要使用類似下面的“簡單樹”(不包含 Value 這一層)結構:
{id: 1,name: "宇宙科技有限公司",parentId: 0,//...children[{id: 2,name: "人事部",parentId: 1,//...children:[...]}]
}
提供一個 API 可以方便獲取一個類似的簡單樹的拷貝:
Department rootDept = TreeUtil.getSimpleTreeCopy(TestData.tree, Department.class, false);
String jsonString = JSON.toJSONString(rootDept);
System.out.println(jsonString);
- 因為涉及對象拷貝,所以需要提供一個 Class 對象用于反射。
- 第三個參數可以控制返回的簡單樹中是否關聯父節點(parent字段),一般來說需要 JSON 后返回給前端的話必須是
false
,否則會導致 JSON 格式化工具循環遍歷進而堆棧溢出。- 要操作的數據類型必須有 parent 和 children 屬性以及對應的 Getter/Setter 方法,這里引入一個 Nodeable 接口進行限制,也就是說只有實現了該接口才能使用上面的 API。
其它操作
其它相關的操作和 API 可以直接查看源碼。
反饋&&建議
項目地址為 icexmoon/icexmoon-tree,你可以從這里查看源碼或提交建議。
版本更新記錄
1.0.0
初始版本。
1.0.1
簡化 POM 文件,移除不必要的依賴傳遞。修改 Readme 文件描述內容。
The End.