开发用工具类:无侵入式的树形状工具类TreeUtils
这是处理树形状工具类的工具类,只需要jdk8(现在jdk18都出了,好吧)就可以复制进去用。
我看到的其他TreeUtils太粗糙难用(必须是id和parentId,或者对象实现自定义排序接口,再调用工具类?),就自己写了一个简单基于字符串处理的工具类。非字符串的,可以自己参考,改进。
代码如下:
package cn.jow.extra.tree;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* 无侵入方式生成新的树形状结构数据
* <pre>
* 功能:
* 1:对象数组或map数组 <==>树形状结构数组
* 2:对象数组和map数组查找指定节点的顺序上层节点对象数组
* 3:对象数组或map数组==>筛选指定属性的对象
* 特点:
* 1.不会重新创造对象
* 2.不改变对象排序,若要排序,请先排序好再使用
* 3.仅仅需要jdk8就可以运行,不需要引入其他包
* 4.自测通过,效率不差。(三万条List<Map>数据的用时对比,下边0.038秒,对比递归遍历来源数组30.917秒,)
* (三万条List<T>数据用时对比,下边0.119秒,对比递归遍历来源数组10.349秒)
* 5.都是基于java.util的特定处理代码,不太复杂
* 使用:
* 1.实体对象数组使用的例子:TreeUtils.list2Tree(myObjectList,MyObject::getId,MyObject::getParentId,MyObject::getSonList)
* 2.Map对象数组使用的例子:TreeUtils.list2Tree(mapList,"id","parentId","child")
* 疑问:
* 1.总数不对可能因为环形结构的节点被抛弃
* 2.总数不对可能因为有父节点id但父节点不存在的节点会被抛弃
* 3.完全不支持多线程处理同一个源数据数组
* 说明:
* 所见即所得,希望你觉得好用。良心若不痛,删掉作者信息,占为己用都不碍事。
* </pre>
*
* @author JunOneWolf
* @version 1.0
* @date 2022-04-15
*/
public class TreeUtils {
private TreeUtils() {
}
/**
* 根据对象某个属性筛选部分对象
*
* @param srcArray 源对象
* @param equalsStr 比较字符串
* @param attribute 取值字段
* @return 匹配的map数组
*/
public static <T1, T2> List<Map<T1, T2>> findByEqualsAttribute(String equalsStr, List srcArray, String attribute) {
List<Map<T1, T2>> result = new ArrayList<>();
if (srcArray == null || srcArray.size() == 0 || isEmpty(equalsStr) || isEmpty(attribute)) {
return result;
}
for (Object obj : srcArray) {
Map t = (Map) obj;
if (t != null && equalsStr.equals(t.get(attribute))) {
result.add(t);
}
}
return result;
}
/**
* 根据对象某个属性筛选部分对象
*
* @param srcArray 源对象
* @param equalsStr 比较字符串
* @param attribute 取值字段
* @param <T> 泛型
* @return 匹配的字符串属性
*/
public static <T> List<T> findByEqualsAttribute(String equalsStr, List<T> srcArray, Function<T, ? extends String> attribute) {
List<T> result = new ArrayList<>();
if (srcArray == null || srcArray.size() == 0 || isEmpty(equalsStr) || attribute == null) {
return result;
}
for (T t : srcArray) {
if (t != null && equalsStr.equals(attribute.apply(t))) {
result.add(t);
}
}
return result;
}
/**
* 递归往上查找节点,可防止环形结构导致的无限制递归
*
* @param srcArray 单列表节点
* @param equalsId 目标查找id
* @param idField 查找的字段
* @param parentIdField 父节点字段
* @param <T> 泛型
* @return 上层对象节点数组(父, 爷顺序往上)
*/
public static <T> List<T> findParentListById(String equalsId, List<T> srcArray, Function<T, ? extends String> idField, Function<T, ? extends String> parentIdField) {
List<T> result = new ArrayList<>();
if (srcArray == null || srcArray.size() == 0 || idField == null || parentIdField == null) {
return result;
}
Map<String, String> idAndParentIdMap = new HashMap<>(srcArray.size());
Map<String, T> map = new HashMap<>(srcArray.size());
for (T t : srcArray) {
if (t != null) {
String id = idField.apply(t);
String parentId = parentIdField.apply(t);
if (!isEmpty(id)) {
map.put(id, t);
if (!isEmpty(parentId)) {
idAndParentIdMap.put(id, parentId);
}
}
}
}
List<String> userIdList = new ArrayList<>();
findParentListByMap(userIdList, equalsId, idAndParentIdMap);
userIdList.forEach(s -> result.add(map.get(s)));
return result;
}
/**
* 递归往上查找节点,可防止环形结构导致的无限制递归
*
* @param srcArray 单列表节点
* @param idObj 目标查找id
* @param idField 查找的字段
* @param parentIdField 父节点字段
* @return 顺序的上层对象节点数组
*/
public static <T1, T2> List<Map<T1, T2>> findParentListById(Object idObj, List srcArray, String idField, String parentIdField) {
List<Map<T1, T2>> result = new ArrayList<>();
if (srcArray == null || srcArray.size() == 0 || idObj == null || isEmpty(idField) || isEmpty(parentIdField)) {
return result;
}
Map<String, String> idAndParentIdMap = new HashMap<>(srcArray.size());
Map<String, Map<T1, T2>> map = new HashMap<>(srcArray.size());
for (Object obj : srcArray) {
if (obj != null) {
Map t = (Map) obj;
Object id = t.get(idField);
Object parentId = t.get(parentIdField);
if (!isEmpty(id)) {
map.put(id.toString(), t);
if (!isEmpty(parentId)) {
idAndParentIdMap.put(id.toString(), parentId.toString());
}
}
}
}
String equalsId = idObj.toString();
List<String> userIdList = new ArrayList<>();
findParentListByMap(userIdList, equalsId, idAndParentIdMap);
userIdList.forEach(s -> result.add(map.get(s)));
return result;
}
/**
* 树形状结构转化为数组
*
* @param srcList 来源数组
* @param childField 属性字段
* @param <T> 泛型
* @return 对象数组
*/
public static <T> List<T> tree2List(List<T> srcList, Function<T, ? extends List<T>> childField) {
if (srcList == null || srcList.size() == 0 || childField == null) {
return srcList;
} else {
List<T> resultList = new ArrayList<>(srcList.size());
for (T t : srcList) {
findAllSonByChildField(t, resultList, childField);
}
return resultList;
}
}
/**
* 树形状转化单一列表
*
* @param srcList 来源对象
* @param childField 子节点对象
* @param <T1> 泛型
* @param <T2> 泛型
* @return 单列表数组
*/
public static <T1, T2> List<Map<T1, T2>> tree2List(List srcList, String childField) {
if (srcList == null || srcList.size() == 0 || isEmpty(childField)) {
return srcList;
} else {
List<Map<T1, T2>> resultList = new ArrayList<>(srcList.size());
for (Object obj : srcList) {
Map t = (Map) obj;
findAllSonByChildField(t, resultList, childField);
}
return resultList;
}
}
/**
* 生成树形状结构数据
*
* @param srcList 对象数组
* @param idName 当前节点标识
* @param parentIdName 上层节点标识
* @param childName 存储的子节点数组
* @return 目标对象
*/
public static <T1, T2> List<Map<T1, T2>> list2Tree(List srcList, String idName, String parentIdName, String childName) {
if (srcList == null || srcList.size() == 0 || isEmpty(idName) || isEmpty(parentIdName) || isEmpty(childName)) {
return srcList;
} else {
if (idName.equals(parentIdName) || idName.equals(childName) || parentIdName.equals(childName)) {
throw new RuntimeException("编码异常,三个字段不能任意相等");
}
List<Map<T1, T2>> resultList = new ArrayList<>();
Map<String, List<Map<T1, T2>>> parentIdAndObjListMap = new HashMap<>(srcList.size());
for (Object obj : srcList) {
Map<T1, T2> map = (Map<T1, T2>) obj;
if (map == null || isEmpty(map.get(idName))) {
continue;
}
if (isEmpty(map.get(parentIdName))) {
resultList.add(map);
} else {
List<Map<T1, T2>> tempList = parentIdAndObjListMap.computeIfAbsent(map.get(parentIdName).toString(), k -> new ArrayList<>());
tempList.add(map);
}
}
for (Map<T1, T2> s : resultList) {
insertSonList2ChildAttribute(s, parentIdAndObjListMap, idName, childName);
}
return resultList;
}
}
/**
* 生成树形状数组
*
* @param srcList 源数组
* @param idField 字段
* @param parentIdField 父字段
* @param childField 字节点的存储位置
* @param <T> 泛型
* @return 新的结构后数据数组
*/
public static <T> List<T> list2Tree(List<T> srcList, Function<T, ? extends String> idField, Function<T, ? extends String> parentIdField, Function<T, ? extends List<T>> childField) {
if (srcList == null || srcList.size() == 0 || idField == null || parentIdField == null || childField == null) {
return srcList;
} else {
List<T> resultList = new ArrayList<>(srcList.size());
Map<String, List<T>> parentIdAndObjListMap = new HashMap<>(srcList.size());
for (T t : srcList) {
if (t == null || isEmpty(idField.apply(t))) {
continue;
}
if (childField.apply(t) == null) {
throw new RuntimeException("编码异常,实体必须初始化,比如:private List child = new ArrayList ();");
}
String parentId = parentIdField.apply(t);
if (isEmpty(parentId)) {
resultList.add(t);
} else {
List<T> tempList = parentIdAndObjListMap.computeIfAbsent(parentId, k -> new ArrayList<>());
tempList.add(t);
}
}
resultList.forEach(s -> insertSonList2ChildAttribute(s, parentIdAndObjListMap, idField, childField));
return resultList;
}
}
private static <T> void insertSonList2ChildAttribute(T t, Map<String, List<T>> parentIdAndObjListMap, Function<T, ? extends String> idField, Function<T, ? extends List<T>> childField) {
String id = idField.apply(t);
List<T> tempObjList = parentIdAndObjListMap.get(id);
if (tempObjList != null) {
childField.apply(t).addAll(tempObjList);
for (T tempObj : tempObjList) {
insertSonList2ChildAttribute(tempObj, parentIdAndObjListMap, idField, childField);
}
}
}
private static void insertSonList2ChildAttribute(Map t, Map allList, String idName, String childName) {
String id = t.get(idName).toString();
List mapList = (List) allList.get(id);
if (mapList != null) {
t.put(childName, mapList);
for (Object o : mapList) {
insertSonList2ChildAttribute((Map) o, allList, idName, childName);
}
}
}
private static boolean isEmpty(Object str) {
return str == null || str.toString().length() == 0;
}
private static void findParentListByMap(List<String> userIdList, String id, Map<String, String> idAndParentIdMap) {
if (idAndParentIdMap.containsKey(id)) {
String parentId = idAndParentIdMap.get(id);
//防止环形数据
if (userIdList.contains(parentId)) {
return;
}
userIdList.add(parentId);
findParentListByMap(userIdList, parentId, idAndParentIdMap);
}
}
private static <T> void findAllSonByChildField(T t, List<T> keepList, Function<T, ? extends List<T>> childField) {
keepList.add(t);
if (t == null) {
return;
}
List<T> childSon = childField.apply(t);
if (childSon != null) {
for (T tempObject : childSon) {
findAllSonByChildField(tempObject, keepList, childField);
}
}
}
private static void findAllSonByChildField(Map t, List keepList, String childField) {
keepList.add(t);
if (t == null) {
return;
}
List<Map> childSon = (List<Map>) t.get(childField);
if (childSon != null) {
for (Map tempObject : childSon) {
findAllSonByChildField(tempObject, keepList, childField);
}
}
}
}
使用例子:
public static void main(String[] args) {
//lombok生成Get/Set方法(或者删除这两个标签,自己生成get/set)
@Getter
@Setter
class Money {
private String id;
private String value;
private List<Money> child = new ArrayList<>();
public Money(String id, String value) {
this.id = id;
this.value = value;
}
public String toString() {
return "Money{" +
"id='" + id + '\'' +
(value == null ? "" : (", value='" + value + '\'')) +
(child.size() > 0 ? child.toString() : "") +
'}';
}
}
List<Money> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
int v = (int) (Math.random() * i);
list.add(new Money(String.valueOf(i), v == 0 ? null : String.valueOf(v)));
}
list.forEach(System.out::println);
System.out.println("-----处理树形状-----");
List<Money> tree = TreeUtils.list2Tree(list, Money::getId, Money::getValue, Money::getChild);
System.out.println(tree);
//随机生成的树结构数组的打印数据如下
//Money{id='0'}
//Money{id='1'}
//Money{id='2'}
//Money{id='3', value='2'}
//Money{id='4', value='1'}
//-----处理树形状-----
//[Money{id='0'}, Money{id='1'[Money{id='4', value='1'}]}, Money{id='2'[Money{id='3', value='2'}]}]
}

浙公网安备 33010602011771号