JAVA构建树形结构通用工具类
场景说明
在日常java开发过程中,经常遇到要把一个扁平结构的数据,列入菜单,部门,区域等需要处理成为树形结构返给前台,因此可以写一个公共的util来实现。特此写一篇博客来记录解决问题的思路以及过程
文章最后贴出了完整的代码,直接复制两个类即可在项目当中使用
案例中的方法说明
用了java 8的最新特性lambda表达式,可以了解下其实很简单。
===== list.stream
吧list转化为一个流进行操作
===== list.stream.filter(c -> c.getParentId() != null)
filter 选择出满足条件的元素
构建思路实现
要处理树形结构,对应的表记录和实体类中至少满足以下条件才可以处理
:实体类或表中要包含类似如下的字段
表中结构:节点id:node 、父节点id: parentId 、节点名称:nodeName
实体类中还要多一个childrenList的字段,可以加在Vo里面
第一步:构建接口TreeFeature
若要实现通用构建,则肯定需要获得实体类的parentId,nodeId,childrenList,但是每个实体类或Vo的名字不一样。怎么办呢。因此我想到的是定义一个接口,让需要构建树形结构的实体来实现。当中就有获得nodeId,设置子集的方法。接口定义如下
/**
* 定义一个接口,如果需要构建一颗树需要实现该接口
* 实现接口的需要传入一个泛型参数T ,表示该类实现了该接口
*/
public interface TreeFeature<T extends TreeFeature> {
/**
* 获取子节点
*/
List<T> getChildrenList();
/**
* 获取当前节点
*/
String findNodeId();
/**
* 获取父节点
*/
String findParentId();
/**
* 设置子节点
*/
void putChildrenList(List<T> children);
/**
* 树形结构名称
*/
String getTreeLable();
第二步:让需要构建树形结构的类实现该接口并实现方法
public class AreaDemo implements TreeFeature {
//区域编码
private String baCode;
//区域名称
private String baName;
//父节点
private String baParentId;
//子节点
private List<Area> childrenList;
@Override
public List getChildrenList() {
return this.childrenList;
}
@Override
public String findNodeId() {
return this.baCode;
}
@Override
public String findParentId() {
return this.baParentId;
}
@Override
public void putChildrenList(List children) {
this.childrenList = children;
}
@Override
public String getTreeLable() {
return this.baName;
}
第三部创建通用的TreeUtil工具类
1.首先创建一个build方法供外部调用,接受参数为实现了TreeFeature接口的List,和一个根节点参数,返回值为构建好的list,方法先初步筛选出根节点和所有非根节点
if(!CollectionUtils.isEmpty(allList)){
//1.获取所有根节点
List<V> roots = allList.stream().filter(c -> c.getParentId().equals(rootNodeId)).filter(c -> c.getParentId() == null).collect(Collectors.toList());
//2.获取所有非根节点
List<V> others = allList.stream().filter(c -> c.getParentId() != null).collect(Collectors.toList());
return buildTree(roots,others);
}
return new ArrayList<>();
}
2.创建buildTree方法,参数为根节点集合以及非根节点的集合,思路为遍历根节点,吧根节点下的子集加入其中
if(!CollectionUtils.isEmpty(roots)){
Map<String,String> map = new ConcurrentHashMap<>();
//从根节点向下遍历
roots.forEach(beanTree -> addChildren(others,beanTree,map));
return roots;
}
return new ArrayList<>();
}
3.核心关键点来了addChildren为核心方法,顾名思义就是吧子节点加入到根节点当中,因此初步确定该方法接受参数为两个,一个集合,一个根节点。另外为了减少遍历次数,传入一个记录了操作过节点的map
* 把根节点所属的子节点加入其中
* @param others
* @param beanTree
* @param map
* @param <V>
*/
public static <V extends TreeFeature2<V>> void addChildren(List<V> others,V beanTree,Map map){
//定义一个子集集合,最后会调用实体的putChildrenList,给实体的childrenList赋值
List<V> childrenList = new ArrayList<>();
others.stream()
//过滤集合中已经有的节点
.filter(c->!map.containsKey(c.getNode()))
//筛选属于传入的beanTree下的子节点,元素的parentId = beanTree.nodeID
.filter(c->c.getNode().equals(beanTree.getParentId()))
//至此已经从others中过滤出了beanTree下的子节点集合,开始遍历并加入childreList
.forEach(item -> {
//吧该节点放入map
map.put(item.getNode(),item.getParentId());
//递归调用addChildren,继续把子节点的子集加入
addChildren(others,beanTree,map);
childrenList.add(item);
});
//调用beanTree的putChildrenList 给该节点赋值
beanTree.putChildrenList(childrenList);
}
到这位置,TreeUtil已经完成来测试,先在实体类中添加点测试数据
* 初始化
*/
private static List<Area> areaList = new ArrayList<>();
static {
areaList.add( new Area("530000","云南省","0"));
areaList.add( new Area("530100","昆明市","530000"));
areaList.add( new Area("530109","曲靖市","530000"));
areaList.add( new Area("530211","麒麟区","530109"));
areaList.add( new Area("530212","麒麟区2","530109"));
areaList.add( new Area("530101","昆明市市辖区","530100"));
areaList.add( new Area("530102","五华区","530100"));
areaList.add(new Area("530102","盘龙区","530100"));
areaList.add( new Area("110000","北京市","0"));
areaList.add( new Area("110100","北京市辖区","110000"));
areaList.add( new Area("110100","北京市辖区","110000"));
areaList.add( new Area("110101","东城区","110100"));
areaList.add( new Area("110102","西城区","110100"));
}
public Area() {
}
public Area(String baCode, String baName, String baParentId) {
this.baCode = baCode;
this.baName = baName;
this.baParentId = baParentId;
}
编写测试类
public static void main(String[] args) {
List<Area> areaList = Area.getAreaList();
List<Area> build = TreeUtilDemo.build(areaList, "0");
build.stream().forEach(item->{
System.out.println(item);
});
}
}
输出结果
Area{baCode='110000', baName='北京市', baParentId='0', childrenList=[Area{baCode='110100', baName='北京市辖区', baParentId='110000', childrenList=[Area{baCode='110101', baName='东城区', baParentId='110100', childrenList=[]}, Area{baCode='110102', baName='西城区', baParentId='110100', childrenList=[]}]}]}
当有一个新的需要构建树形结构的实体时,只需要实现TreeFeature接口并重写好其中的方法,调用TreeUtil .build即可
完整类的代码如下
TreeVoFeature类
public interface TreeVoFeature<T extends TreeVoFeature> {
/**
* 设置子节点
*
* @param children 子节点
*/
void putChildrenList(List<T> children);
/**
* 获取子节点
*
* @return 父级节点
*/
List<T> getChildrenList();
/**
* 获取当前节点
*
* @return 当前节点标识
*/
String findNodeId();
/**
* 获取父级节点
*
* @return 父级节点
*/
String findParentNodeId();
/**
* 树形结构名称
*
* @return 树形结构名称
*/
String getTreeLabel();
}
TreeUtil类
public class TreeUtil {
/**
* 构建TreeSelect
* @param lists
* @param <V>
* @return
*/
public static <V extends TreeVoFeature<V>> List<TreeSelect> buildTreeSelect(List<V> lists) {
return addChildren(lists);
}
/**
* 递归TreeSelect
* @param lists
* @param <V>
* @return
*/
private static <V extends TreeVoFeature<V>> List<TreeSelect> addChildren(List<V> lists) {
List<TreeSelect> treeSelects = Lists.newArrayList();
lists.stream().forEach(c -> {
TreeSelect treeSelect = new TreeSelect();
treeSelect.setId(Long.valueOf(c.findNodeId()));
treeSelect.setLabel(c.getTreeLabel());
treeSelect.setChildren(addChildren(c.getChildrenList()));
treeSelects.add(treeSelect);
});
return treeSelects;
}
/**
* 构建树形结构
* @param trees
* @param rootNodeId
* @param <V>
* @return
*/
public static <V extends TreeVoFeature<V>> List<V> build(List<V> trees, String rootNodeId) {
if (CollectionUtils.isEmpty(trees)) {
return new ArrayList<>();
}
//根节点
List<V> roots = trees.stream().filter(node -> node.findParentNodeId().equals(rootNodeId)).collect(Collectors.toList());
//其他节点
List<V> others = trees.stream().filter(node -> node.findParentNodeId() != null).collect(Collectors.toList());
return buildTree(roots, others);
}
/**
* 生成树形结构
* @param roots 根节点
* @param others 子节点
* @param <V>
* @return
*/
private static <V extends TreeVoFeature<V>> List<V> buildTree(List<V> roots, List<V> others) {
if (!CollectionUtils.isEmpty(others)) {
//声明一个map,用来过滤已操作过的数据
Map<String, String> map = Maps.newConcurrentMap();
//逻辑其实很简单,从根节点往下遍历,加上所有的根节点
roots.forEach(beanTree -> addChildren(others, beanTree, map));
return roots;
}
return Lists.newArrayList();
}
/**
* 获取子节点列表
* @param others 所有子节点
* @param beanTree 根节点
* @param map 过滤已加入过的节点
* @param <V>
*/
private static <V extends TreeVoFeature<V>> void addChildren(List<V> others, V beanTree, Map<String, String> map) {
List<V> childList = Lists.newArrayList();
others.stream()
//判断是否已经被处理过了
.filter(c -> !map.containsKey(c.findNodeId()))
//获取当前节点的子节点
.filter(c -> c.findParentNodeId().equals(beanTree.findNodeId()))
//子节点下所有的节点,也需要加进去
.forEach(c -> {
map.put(c.findNodeId(), c.findParentNodeId());
addChildren(others, c, map);
childList.add(c);
});
beanTree.putChildrenList(childList);
}
}