泛型的使用-方法抽取

最近的需求中,需要在构建的树结构中增加每一级的层级level,之前使用的hutools TreeUtil.build 中需要数据库中有字段记录,才能赋值过去,但是增加字段后新增、编辑等逻辑需要改动。

# 对新增的level字段,赋值
UPDATE sys_management AS t1
JOIN (
    SELECT id, parent_id, @level := IF(parent_id = 0, 1, @level + 1) AS level
    FROM sys_management
    ORDER BY id
) AS t2 ON t1.id = t2.id
SET t1.level = t2.level;
1
2
3
4
5
6
7
8

所以编写了简单的构建方法并抽取成公共方法。

/**
 * 
 * @param dataList
 * @param parentId
 * @param level
 * @return OrganizationTreeResult 为接收实体
 */
private static List<OrganizationTreeResult> buildTreeLevel(List<OrganizationTreeResult> dataList, int parentId, int level) {
    List<OrganizationTreeResult> treeList = new ArrayList<>();
    for (OrganizationTreeResult data : dataList) {
        if (data.getParentId() == parentId) {
            data.setLevel(level);
            data.setChildren(buildTreeLevel(dataList, data.getId(), level + 1));
            treeList.add(data);
        }
    }
    return treeList;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

将上面方法中修改为公共方法,OrganizationTreeResult 修改为泛型,使用了泛型来代替OrganizationTreeResult,使得这个方法可以适用于不同类型的数据。

在泛型方法中,无法直接调用对象的方法或访问对象的属性,因为泛型类型是未知的。为了解决这个问题,可以使用一个接口TreeData来定义对象的通用方法,其中包括getParentId()、getId()、setLevel()和setChildren(),然后将对象实现该接口。修改后的代码如下所示:

private static <T extends TreeData> List<T> buildTreeLevel(List<T> dataList, int parentId, int level) {
    List<T> treeList = new ArrayList<>();
    for (T data : dataList) {
        if (data.getParentId() == parentId) {
            data.setLevel(level);
            List<T> children = buildTreeLevel(dataList, data.getId(), level + 1);
            data.setChildren(children);
            treeList.add(data);
        }
    }
    return treeList;
}
1
2
3
4
5
6
7
8
9
10
11
12

需要将OrganizationTreeResult类实现TreeData接口,并在接口中定义这些方法。这样,就可以在泛型方法中调用这些通用方法了。

进一步改进

中的TreeData使用传参提供。

使用了反射来设置泛型对象的属性,以适应不同类型对象的属性命名。

private static <T> List<T> buildTreeLevel(List<T> dataList, int parentId, int level, Class<T> dataType) {
    List<T> treeList = new ArrayList<>();
    for (T data : dataList) {
        try {
            Method getParentIdMethod = dataType.getMethod("getParentId");
            int dataParentId = (int) getParentIdMethod.invoke(data);
            if (dataParentId == parentId) {
                Method setLevelMethod = dataType.getMethod("setLevel", Integer.class);
                setLevelMethod.invoke(data, level);
                
                Method getIdMethod = dataType.getMethod("getId");
                int dataId = (int) getIdMethod.invoke(data);
                List<T> children = buildTreeLevel(dataList, dataId, level + 1, dataType);
                
                Method setChildrenMethod = dataType.getMethod("setChildren", Object.class);
                setChildrenMethod.invoke(data, children);
                
                treeList.add(data);
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    return treeList;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

在这个修改后的方法中,添加了一个额外的参数dataType,用于传递TreeData的类型。

通过使用反射,我们可以在运行时获取并调用对象的方法。

这样,就可以在调用方法时指定TreeData的具体类型,使方法适用于不同的数据类型。

/**
 * 组装树结构(带层级)
 *
 * @param dataList
 * @param parentId
 * @param level
 * @param dataType
 * @return
 */
private static <T> List<T> buildTreeLevel(List<T> dataList, int parentId, int level, Class<T> dataType) {
    List<T> treeList = new ArrayList<>();
    for (T data : dataList) {
        try {
            Method getIdMethod = dataType.getMethod("getId");
            int dataId = (int) getIdMethod.invoke(data);
            if (hasChildren(dataList, dataId, dataType)) {
                Method setLevelMethod = dataType.getMethod("setLevel", Integer.class);
                setLevelMethod.invoke(data, level);

                List<T> children = buildTreeLevel(dataList, dataId, level + 1, dataType);
                Method setChildrenMethod = dataType.getMethod("setChildren", Object.class);
                setChildrenMethod.invoke(data, children);

                treeList.add(data);
            } else {
                Method setLevelMethod = dataType.getMethod("setLevel", Integer.class);
                setLevelMethod.invoke(data, level);

                Method setChildrenMethod = dataType.getMethod("setChildren", Object.class);
                List<T> emptyTreeList = new ArrayList<>();
                setChildrenMethod.invoke(data, emptyTreeList);
                treeList.add(data);
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    return treeList;
}

/**
 * 是否存在下级
 *
 * @param dataList
 * @param parentId
 * @param dataType
 * @param <T>
 * @return
 */
private static <T> boolean hasChildren(List<T> dataList, int parentId, Class<T> dataType) {
    for (T data : dataList) {
        try {
            Method getParentIdMethod = dataType.getMethod("getParentId");
            int dataParentId = (int) getParentIdMethod.invoke(data);
            if (dataParentId == parentId) {
                return true;
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
最近修改于: 2024/6/9 23:54:13
    和宇宙温柔的关联
    房东的猫