list 生成tree java_【Java工具类】List转Tree通用工具类

一、背景

最近的开发工作用到“树”模型比较多,例如节点树、权限树等。

每一个实体都有自己特殊的字段,所以最初的解决方法是给每一个实体写独特的转换方法。

然后需要转换为树的实体变多,代码就会冗余,因此写一个工具类,提供对应的方法将常见的List转换为Tree。

二、实现

1.效果

UI框架:layui(官方文档:https://www.layui.com/doc/)

在使用layui的过程中发现,目前layui的组件功能并没有很强大,比如树组件就不能实现“只选单个”,但是layui简单易用,源码也不复杂,因此可以通过修改源码来达到目的。

后续计划将自己在使用layui过程中遇到的问题和解决方法写出来分享,欢迎各位码友关注学习。

ca83c90b39cb

简单的节点树.png

2.编码实现

2.1 实体TreeDot

前端框架实现树组件,一般都有自己独特的数据源格式。从layui文档可看到layui实现树组件的数据源格式:

ca83c90b39cb

layui树形组件数据源格式.png

因此,首先建一个实体TreeDot,那么将这个实体返回前端就可以直接渲染成树。使用其他的UI框架类推。

package pri.xiaowd.layui.pojo;

import lombok.Data;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

/***

* 树点

* 需要实现树的类可以继承该类,手写set方法,在设定本身属性值时同时设置该类中的相关属性

* @ClassName TreeTreeDot

* @Author xiaowd

* @DateTime 2020/2/23 15:58

*/

@Data

public class TreeDot {

/**

* 点ID

*/

private String id;

/**

* 点名称

*/

private String title;

/**

* 父点ID

*/

private String parentId;

/**

* 该点是否展开,默认不展开

*/

private Boolean spread = false;

/**

* 该点是否选中,默认不选中

*/

private Boolean checked = false;

/**

* 该点的图标,默认不设置

*/

private String icon;

/**

* 该点的其他属性

*/

private Map attributes = new HashMap<>();

/**

* 该点的子树集合

*/

private List> children = new ArrayList<>();

}

TreeDot类的字段和文档提供的字段可能不完全一样,这个根据自己的实际需求来定就行。最为重要的是 id、title、children 三个字段,layui根据这三个字段就可以构建一颗树,其他都可以不要,也可以新增自己额外需要的,例如 parentId、attributes ,新增的字段不会影响树的构建。

另外,这里用到了泛型,因为需要适应所有需要转换为树的实体。

2.2 实体Node

建自己需要转换成树的原始实体,一般就是从数据源读取的数据。这个实体需要继承上面建的TreeDot,并重写set方法。这里以Node为例:

package pri.xiaowd.layui.pojo;

import lombok.Data;

/***

* 节点

* @ClassName Node

* @Author xiaowd

* @DateTime 2020/1/31 15:37

*/

@Data

public class Node extends TreeDot {

/**

* 节点编码

*/

private Integer nodeId;

/**

* 节点名称

*/

private String nodeName;

/**

* 父节点编码

*/

private Integer parentNodeId;

/**

* 创建时间

*/

private Long time;

public void setNodeId(Integer nodeId) {

this.nodeId = nodeId;

super.setId(String.valueOf(nodeId));

}

public void setNodeName(String nodeName) {

this.nodeName = nodeName;

super.setTitle(nodeName);

}

public void setParentNodeId(Integer parentNodeId) {

this.parentNodeId = parentNodeId;

super.setParentId(String.valueOf(parentNodeId));

}

public void setTime(Long time) {

this.time = time;

super.getAttributes().put("time",time);

}

}

继承TreeDot之后,也就拥有了TreeDot的字段,因此重写set方法,将字段的值同时赋给TreeDot的字段。这里有一个小技巧,因为必要的字段是id和title,那么多余的字段就可以统一放在一个集合中attributes。注意,attributes需要一开始就实例化,不然会报错哦。

2.3 工具类TreeDotUtils

接下来就是具体的工具类了,这个工具类是通用的,因此需要使用泛型(同时使用了递归,就这两个重要的知识点),只要是继承了TreeDot并重写set方法的实体都可以转化:

package pri.xiaowd.layui.util;

import pri.xiaowd.layui.pojo.TreeDot;

import java.util.ArrayList;

import java.util.HashSet;

import java.util.List;

import java.util.Set;

/***

* 操作“树”的工具

* @ClassName TreeDotUtils

* @Author xiaowd

* @DateTime 2020/4/22 9:14

*/

public class TreeDotUtils {

/**

* 将List转换为Tree

* @MethosName convertListToTreeDot

* @Author xiaowd

* @Date 2020/4/22 9:17

* @param tList

* @return java.util.List>

*/

public static List> convertListToTreeDot(List tList){

List> treeDotList = new ArrayList<>();

if(tList != null && tList.size() > 0){

for(T t:tList){

if(!isTreeDotExist(tList,t.getParentId())){

//不存在以父ID为ID的点,说明是当前点是顶级节点

TreeDot tTreeDot = getTreeDotByT(t, tList);

treeDotList.add(tTreeDot);

}

}

}

return treeDotList;

}

/**

* 根据ID判断该点是否存在

* @MethosName isTreeDotExist

* @Author xiaowd

* @Date 2020/4/22 9:50

* @param tList

* @param id 点ID

* @return java.lang.Boolean

*/

private static Boolean isTreeDotExist(List tList, String id) {

for(T t:tList){

if(t.getId().equals(id)){

return true;

}

}

return false;

}

/**

* 获取指定父点的子树

* @MethosName getChildTreeList

* @Author xiaowd

* @Date 2020/4/22 10:02

* @param parentTreeDot 父点

* @param tList

* @return java.util.List>

*/

private static List> getChildTreeDotList(TreeDot parentTreeDot, List tList){

List> childTreeDotList = new ArrayList<>();

for(T t:tList){

if(parentTreeDot.getId().equals(t.getParentId())){

//如果父ID是传递树点的ID,那么就是传递树点的子点

TreeDot tTreeDot = getTreeDotByT(t,tList);

childTreeDotList.add(tTreeDot);

}

}

return childTreeDotList;

}

/**

* 根据实体获取TreeDot

* @MethosName getTreeDotByT

* @Author xiaowd

* @Date 2020/5/4 22:17

* @param t

* @param tList

* @return pri.xiaowd.layui.pojo.TreeDot

*/

private static TreeDot getTreeDotByT(T t,List tList){

TreeDot tTreeDot = new TreeDot<>();

tTreeDot.setId(t.getId());

tTreeDot.setParentId(t.getParentId());

tTreeDot.setTitle(t.getTitle());

tTreeDot.setChecked(t.getChecked());

tTreeDot.setIcon(t.getIcon());

tTreeDot.setAttributes(t.getAttributes());

tTreeDot.setChildren(getChildTreeDotList(tTreeDot,tList));

return tTreeDot;

}

/**

* 获取根据指定ID所在点为父点的树

* @MethosName getTreeDotById

* @Author xiaowd

* @Date 2020/4/22 15:00

* @param id

* @param treeDotList

* @return cn.eshore.common.entity.TreeDot

*/

public static TreeDot getTreeDotById(String id,List> treeDotList){

if(id != null && !"".equals(id) && treeDotList != null && treeDotList.size() > 0){

for(TreeDot treeDot:treeDotList){

if(id.equalsIgnoreCase(treeDot.getId())){

return treeDot;

}

if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){

TreeDot td = getTreeDotById(id, treeDot.getChildren());

if(td != null){

return td;

}

}

}

}

return null;

}

/**

* 将TreeList的所有点转换为ID的Set集合

* @MethosName convertTreeDotToIdSet

* @Author xiaowd

* @Date 2020/4/22 16:13

* @param treeDotList

* @param kClass ID的类型

* @return java.util.Set

*/

public static Set convertTreeDotToIdSet(List> treeDotList,Class kClass){

Set idSet = new HashSet<>();

if(treeDotList != null && treeDotList.size() > 0){

for(TreeDot treeDot:treeDotList){

idSet.add((K)treeDot.getId());

if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){

idSet.addAll(convertTreeDotToIdSet(treeDot.getChildren(),kClass));

}

}

}

return idSet;

}

/**

* 将Tree(单点)的所有点转换为ID的Set集合

* @MethosName convertTreeDotToIdSet

* @Author xiaowd

* @Date 2020/4/29 9:08

* @param treeDot

* @param kClass

* @return java.util.Set

*/

public static Set convertTreeDotToIdSet(TreeDot treeDot,Class kClass){

Set idSet = new HashSet<>();

if(treeDot != null){

idSet.add((K)treeDot.getId());

if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){

idSet.addAll(convertTreeDotToIdSet(treeDot.getChildren(),kClass));

}

}

return idSet;

}

}

在需要转化时,只需要调用 convertListToTreeDot(List tList) 方法即可。参数List是原始的实体集合。

该方法在拿到原始的实体集合List后,进行遍历,通过将遍历得到的实体的父ID传入 isTreeDotExist(List tList, String id) 方法判断该实体的父实体是否存在,如果不存在说明就是根实体(PS.根实体也是一个集合,根不一定就只有一个);

找到根实体之后,通过 getTreeDotByT(T t,List tList) 方法将该实体转换为树点TreeDot对象;该TreeDot对象中的children字段是子树集合,通过 getChildTreeDotList(TreeDot parentTreeDot, List tList) 获得;

getChildTreeDotList方法使用了递归。有两个参数,第一个是树点TreeDot,要获取哪个树点的子树集合,这个参数就传哪个树点;第二个是原始的实体集合List。

遍历原始的实体集合List,如果遍历得到的实体的父ID是传入的树点的ID,那么这个实体就是传入树点的子树集合其中一员,因此将该实体转换为树点并放到集合中,返回集合作为传入树点的子树集合。

因为这个转换出来的树点也有自己的子树集合,那么就需要通过递归获取,只是第一个参数已经变成了这个转换出来的树点。

工具类中还额外提供了集合方法:

getTreeDotById(String id,List> treeDotList) : 根据ID获取树点;

convertTreeDotToIdSet(List> treeDotList,Class kClass) : 根据树集合(根有一到多个)获取其ID的Set集合;

convertTreeDotToIdSet(TreeDot treeDot,Class kClass) : 根据树(根只有一个)获取其ID的Set集合。

2.4 测试

测试的话就不从数据库拿到List了,手动建即可。

controller

@RequestMapping("/tree")

@ResponseBody

public Map tree(){

//国节点 中国

Node rootNode = new Node();

rootNode.setNodeId(110000);

rootNode.setNodeName("中国");

rootNode.setParentNodeId(0);

rootNode.setTime(System.currentTimeMillis());

//省节点 广东

Node pNode1 = new Node();

pNode1.setNodeId(120000);

pNode1.setNodeName("广东");

pNode1.setParentNodeId(110000);

pNode1.setTime(System.currentTimeMillis());

//市节点 广州

Node cNode1 = new Node();

cNode1.setNodeId(120001);

cNode1.setNodeName("广州");

cNode1.setParentNodeId(120000);

cNode1.setTime(System.currentTimeMillis());

//区节点 广州

Node aNode1 = new Node();

aNode1.setNodeId(1200011);

aNode1.setNodeName("天河区");

aNode1.setParentNodeId(120001);

aNode1.setTime(System.currentTimeMillis());

//子节点 湖南

Node pNode2 = new Node();

pNode2.setNodeId(130000);

pNode2.setNodeName("湖南");

pNode2.setParentNodeId(110000);

pNode2.setTime(System.currentTimeMillis());

//市节点 长沙

Node cNode2 = new Node();

cNode2.setNodeId(130001);

cNode2.setNodeName("长沙");

cNode2.setParentNodeId(130000);

cNode2.setTime(System.currentTimeMillis());

//子节点 上海

Node pNode3 = new Node();

pNode3.setNodeId(140000);

pNode3.setNodeName("上海");

pNode3.setParentNodeId(110000);

pNode3.setTime(System.currentTimeMillis());

List nodeList = new ArrayList<>();

nodeList.add(rootNode);

nodeList.add(pNode1);

nodeList.add(cNode1);

nodeList.add(aNode1);

nodeList.add(pNode2);

nodeList.add(cNode2);

nodeList.add(pNode3);

//转换

List> nodeTreeDotList = TreeDotUtils.convertListToTreeDot(nodeList);

Map result = new HashMap<>();

result.put("nodeTreeDotList",nodeTreeDotList);

return result;

}

js

var ctxPath=[[${#httpServletRequest.getContextPath()}]];

layui.use(['tree'],function(){

var tree = layui.tree;

tree.render({

elem: '#node-tree',

id: 'node-tree',

data: getNodeTreeDot(),

onlyIconControl: true,

edit: ['add'],

customOperate: true,

text: {

none: '无节点'

}

});

});

function getNodeTreeDot() {

var data = [];

layui.jquery.ajax({

url: ctxPath + "/tree",

type: "post",

async: false,

success: function(result){

data = result.nodeTreeDotList;

}

});

console.log(data)

return data;

}

效果

ca83c90b39cb

最终展示效果.png

三、结语

将List转换为树有不同的写法,但是基本的思路是一样的,将这种转换写成一个工具类,我认为对减少代码冗余、优化代码美观程度、提高编码效率都有较大的帮助。如果各位码友有自己的想法,或者觉得我的写法还可以再优化,欢迎给我留言,共同交流。

交流邮箱:weidag_xiao@sina.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值