java之TreeUtils生成一切对象树形结构案例

项目中经常会遇到各种需要以树形结构展示的功能,比较常见的,如菜单树,分类树,部门树等等,如果为每种类型都遍历递归生成树形结构返回给前端,显得有些冗余且麻烦,并且其实逻辑都是一致的,只是遍历的对象不同而已,故其实可以通过面向接口思维,来实现这种通用工具类的实现。

TreeNode用来表示每个树节点的抽象,即需要生成树的对象需要实现此接口。

 /**
   * 树节点父类,所有需要使用{@linkplain TreeUtils}工具类形成树形结构等操作的节点都需要实现该接口
   *
   * @param <T> 节点id类型
   */
  public interface TreeNode<T> {
    /**
     * 获取节点id
     *
     * @return 树节点id
     */
    T id();
    /**
     * 获取该节点的父节点id
     *
     * @return 父节点id
     */
    T parentId();
    /**
     * 是否是根节点
     *
     * @return true:根节点
     */
    boolean root();
    /**
     * 设置节点的子节点列表
     *
     * @param children 子节点
     */
    void setChildren(List<? extends TreeNode<T>> children);
    /**
     * 获取所有子节点
     *
     * @return 子节点列表
     */
    List<? extends TreeNode<T>> getChildren();
  }

TreeUtils用来生成树形结构,以及获取所有叶子节点等操作

/**
 * 树形结构工具类
 *
 * @author meilin.huang
 * @version 1.0
 * @date 2019-08-24 1:57 下午
 */
public class TreeUtils {
  /**
   * 根据所有树节点列表,生成含有所有树形结构的列表
   *
   * @param nodes 树形节点列表
   * @param <T>  节点类型
   * @return 树形结构列表
   */
  public static <T extends TreeNode<?>> List<T> generateTrees(List<T> nodes) {
    List<T> roots = new ArrayList<>();
    for (Iterator<T> ite = nodes.iterator(); ite.hasNext(); ) {
      T node = ite.next();
      if (node.root()) {
        roots.add(node);
        // 从所有节点列表中删除该节点,以免后续重复遍历该节点
        ite.remove();
      }
    }
    roots.forEach(r -> {
      setChildren(r, nodes);
    });
    return roots;
  }
  /**
   * 从所有节点列表中查找并设置parent的所有子节点
   *
   * @param parent 父节点
   * @param nodes 所有树节点列表
   */
  @SuppressWarnings("all")
  public static <T extends TreeNode> void setChildren(T parent, List<T> nodes) {
    List<T> children = new ArrayList<>();
    Object parentId = parent.id();
    for (Iterator<T> ite = nodes.iterator(); ite.hasNext(); ) {
      T node = ite.next();
      if (Objects.equals(node.parentId(), parentId)) {
        children.add(node);
        // 从所有节点列表中删除该节点,以免后续重复遍历该节点
        ite.remove();
      }
    }
    // 如果孩子为空,则直接返回,否则继续递归设置孩子的孩子
    if (children.isEmpty()) {
      return;
    }
    parent.setChildren(children);
    children.forEach(m -> {
      // 递归设置子节点
      setChildren(m, nodes);
    });
  }
  /**
   * 获取指定树节点下的所有叶子节点
   *
   * @param parent 父节点
   * @param <T>  实际节点类型
   * @return 叶子节点
   */
  public static <T extends TreeNode<?>> List<T> getLeafs(T parent) {
    List<T> leafs = new ArrayList<>();
    fillLeaf(parent, leafs);
    return leafs;
  }
  /**
   * 将parent的所有叶子节点填充至leafs列表中
   *
   * @param parent 父节点
   * @param leafs 叶子节点列表
   * @param <T>  实际节点类型
   */
  @SuppressWarnings("all")
  public static <T extends TreeNode> void fillLeaf(T parent, List<T> leafs) {
    List<T> children = parent.getChildren();
    // 如果节点没有子节点则说明为叶子节点
    if (CollectionUtils.isEmpty(children)) {
      leafs.add(parent);
      return;
    }
    // 递归调用子节点,查找叶子节点
    for (T child : children) {
      fillLeaf(child, leafs);
    }
  }
}

具体使用方式之声明树节点对象

@Getter
@Setter
public class ResourceListVO implements TreeNode<Long> {
  private Long id;
  private Long pid;
  private Integer type;
  private String name;
  private String icon;
  private String code;
  private Integer status;
  private List<ResourceListVO> children;
  @Override
  public Long id() {
    return this.id;
  }
  @Override
  public Long parentId() {
    return this.pid;
  }
  @Override
  public boolean root() {
    return Objects.equals(this.pid, 0L);
  }
  @Override
  public void setChildren(List children) {
    this.children = children;
  }
}

具体使用方式之调用

 /**
  * 获取账号的资源树
  */
  public List<ResourceListVO> listByAccountId(Long accountId) {
    return TreeUtils.generateTrees(BeanUtils.copyProperties(mapper.selectByAccountId(userId), ResourceListVO.class));
  }

通过使用TreeUtils工具可以统一方便地生成一切对象的树形结构以及其他一些对树的操作,避免对每个对象都用特定代码生成。使用起来就是几个字简洁方便爽歪歪biu特否。

补充知识:TreeUtil 数据库菜单生成无限级树形结构

1、项目需求:

从数据库从加载所有的菜单出来,菜单中有

id,parentId,name字段

希望能有一个工具帮我进行树形结构重组;

实例类:

package com.lming.chcservice.util;
import lombok.Data;
import java.util.List;
@Data
public class TreeNode {
  /**
   * 节点id
   */
  private String id;
  /**
   * 父节点 默认0为根节点
   */
  private String parentId;
  /**
   * 节点名称
   */
  private String name;
  /**
   * 是否有子节点
   */
  private boolean hasChild;

  public TreeNode(String id, String parentId, String name) {
    this.id = id;
    this.parentId = parentId;
    this.name = name;
  }
}

工具类:

package com.lming.chcservice.util;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 树形结构工具类
 *
 * 将一组list对象转成树形结构
 * 该list需符合设定的字段类型
 *
 */
public class TreeUtil { 

  public static Map<String,Object> mapArray = new LinkedHashMap<String, Object>();
  public List<TreeNode> menuCommon;
  public List<Object> list = new ArrayList<Object>();

  public List<Object> treeMenu(List<TreeNode> menu){
    this.menuCommon = menu;
    for (TreeNode treeNode : menu) {
      Map<String,Object> mapArr = new LinkedHashMap<String, Object>();
      if(treeNode.getParentId().equals("0")){
        setTreeMap(mapArr,treeNode);
        list.add(mapArr);
      }
    }
    return list;
  }

  public List<?> menuChild(String id){
    List<Object> lists = new ArrayList<Object>();
    for(TreeNode a:menuCommon){
      Map<String,Object> childArray = new LinkedHashMap<String, Object>();
      if(a.getParentId() .equals(id)){
        setTreeMap(childArray,a);
        lists.add(childArray);
      }
    }
    return lists;
  }

  private void setTreeMap(Map<String,Object> mapArr,TreeNode treeNode){
    mapArr.put("id", treeNode.getId());
    mapArr.put("name", treeNode.getName());
    mapArr.put("parentId", treeNode.getParentId());
    List<?> childrens = menuChild(treeNode.getId());
    if(childrens.size()>0){
      mapArr.put("hasChild",true);
    }
    else{
      mapArr.put("hasChildren",false);
    }
    mapArr.put("childrens", menuChild(treeNode.getId()));
  }

  public static void main(String[] args){

    List<TreeNode> treeNodeList = new ArrayList<>();
    TreeNode treeNode1 = new TreeNode("1","0","首页");
    TreeNode treeNode2 = new TreeNode("2","0","订单");
    TreeNode treeNode3 = new TreeNode("3","1","预约");
    TreeNode treeNode4 = new TreeNode("4","2","捐献");
    TreeNode treeNode5 = new TreeNode("5","4","我的订单");
    TreeNode treeNode6 = new TreeNode("6","5","个人中心");
    TreeNode treeNode7 = new TreeNode("7","6","个人中心2");
    TreeNode treeNode8 = new TreeNode("8","99","个人中心3");
    treeNodeList.add(treeNode1);
    treeNodeList.add(treeNode6);
    treeNodeList.add(treeNode5);
    treeNodeList.add(treeNode3);
    treeNodeList.add(treeNode4);
    treeNodeList.add(treeNode2);
    treeNodeList.add(treeNode7);
    treeNodeList.add(treeNode8); 

    TreeUtil treeUtil = new TreeUtil();
    System.out.print(JsonUtil.toJson(treeUtil.treeMenu(treeNodeList)));
  }
}

测试结果:

[
 {
  "id": "1",
  "name": "首页",
  "parentId": "0",
  "hasChild": true,
  "childrens": [
   {
    "id": "3",
    "name": "预约",
    "parentId": "1",
    "hasChildren": false,
    "childrens": []
   }
  ]
 },
 {
  "id": "2",
  "name": "订单",
  "parentId": "0",
  "hasChild": true,
  "childrens": [
   {
    "id": "4",
    "name": "捐献",
    "parentId": "2",
    "hasChild": true,
    "childrens": [
     {
      "id": "5",
      "name": "我的订单",
      "parentId": "4",
      "hasChild": true,
      "childrens": [
       {
        "id": "6",
        "name": "个人中心",
        "parentId": "5",
        "hasChild": true,
        "childrens": [
         {
          "id": "7",
          "name": "个人中心2",
          "parentId": "6",
          "hasChildren": false,
          "childrens": []
         }
        ]
       }
      ]
     }
    ]
   }
  ]
 }
]

实力类不一致怎么办? 自己写一个实体转换类,将类的对象属性转换成上面的实体类,然后在调用,当然最快的方式直接修改实体类即可用。

以上这篇java之TreeUtils生成一切对象树形结构案例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

时间: 2020-09-21

java TreeUtil菜单递归工具类

本文实例为大家分享了java TreeUtil菜单递归工具类的具体代码,供大家参考,具体内容如下 菜单树(详细) package com.admin.manager.storeService.util; import com.admin.manager.storeService.entity.Menu; import java.util.ArrayList; import java.util.List; /** * @author m * @date 2019/12/16 */ public c

java实现构造无限层级树形菜单

这里来讲一下后台java如何构造多叉树,这样前台就可接收到数据递归构造树形菜单了. 我们来理一下如何实现构造多叉树的逻辑吧,其实整个问题概括起来就是 1.构造一个实体类,用来存储节点,所以我们构造的需要四个对象(id,pid,name,和chirenList) 2.构造菜单结构 3.构造子菜单,如此循环,通过pid判断上级菜单 具体实现: 1.这里构造Tree.java实体类 package com.tcl.jr.crm.entity; /** * 类名称:Tree * 类描述:树形结构 */

Java Tree结构数据中查找匹配节点方式

我就废话不多说了,大家还是直接看代码吧~ private boolean contains(List<TreeVo> children, String value) { for (TreeVo child : children) { if (child.getName().equals(value) || (child.getChildren().size() > 0 && contains(child.getChildren(), value))) { return t

list转tree和list中查找某节点下的所有数据操作

类的实例化顺序 父类静态变量. 父类静态代码块. 子类静态变量. 子类静态代码块. 父类非静态变量(父类实例成员变量). 父类构造函数. 子类非静态变量(子类实例成员变量). 子类构造函数. 已知组织类Org{String id,String name,String parentId},现在一List<Org>中存放无序的Org数据,求一个组织id下的所有组织. public static List<Org> childList=new ArrayList<>(); p

Java基于正则表达式实现查找匹配的文本功能【经典实例】

本文实例讲述了Java基于正则表达式实现查找匹配的文本功能.分享给大家供大家参考,具体如下: REMatch.java: package reMatch; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by Frank */ public class REMatch { public static void main(String[] args) { String patt = "Q[^

Java下3中XML解析 DOM方式、SAX方式和StAX方式

先简单说下前三种方式: DOM方式:个人理解类似.net的XmlDocument,解析的时候效率不高,占用内存,不适合大XML的解析:SAX方式:基于事件的解析,当解析到xml的某个部分的时候,会触发特定事件,可以在自定义的解析类中定义当事件触发时要做得事情:个人感觉一种很另类的方式,不知道.Net体系下是否有没有类似的方式?StAX方式:个人理解类似.net的XmlReader方式,效率高,占用内存少,适用大XML的解析:不过SAX方式之前也用过,本文主要介绍JAXB,这里只贴下主要代码: 复

Java Pattern和Matcher字符匹配方式

目录 Pattern类定义 因此,典型的调用顺序是: Pattern类方法详解 Pattern类使用示例: Matcher类定义 Matcher类方法详解 1.Matcher类提供了三个匹配操作方法 2.返回匹配器的显示状态 3.int start(),int end(),int group()均有一个重载方法 4.Matcher类同时提供了四个将匹配子串替换成指定字符串的方法: 5.其他一些方法: 应用实例 Pattern类定义 public final class Pattern exten

Java/Js下使用正则表达式匹配嵌套Html标签

通用 HTML 标签区配正则 最近看网站日志,发现有人在博客上转了我不知道几年前写的一个匹配 HTML 标签的正则,刚好最近也在做一些相关的事情,顿时来了兴趣.就拿回来改改,成了下面这样,可能会有一些 case 遗漏,欢迎修改,已知在内嵌 <script> 复杂内容的处理能力较弱,不过对纯 HTML 来说已经够用,拿来做一些分析工具还是不错滴. 复制代码 代码如下: <script type="text/javascript"> var str = "

Java concurrency集合之ConcurrentSkipListMap_动力节点Java学院整理

ConcurrentSkipListMap介绍 ConcurrentSkipListMap是线程安全的有序的哈希表,适用于高并发的场景. ConcurrentSkipListMap和TreeMap,它们虽然都是有序的哈希表.但是,第一,它们的线程安全机制不同,TreeMap是非线程安全的,而ConcurrentSkipListMap是线程安全的.第二,ConcurrentSkipListMap是通过跳表实现的,而TreeMap是通过红黑树实现的. 关于跳表(Skip List),它是平衡树的一种

Java中map内部存储方式解析

Map,即映射,也称为 键值对,有一个 Key, 一个 Value . 比如 Groovy 语言中,  def  map = ['name' : 'liudehua', 'age' : 50 ] ,则 map[ 'name' ]  的值是 'liudehua'. 那么 Map 内部存储是怎么实现的呢?   下面慢慢讲解. 一. 使用 拉链式存储 这个以 Java 中的 HashMap 为例进行讲解.   HashMap 的内部有个数组 Entry[]  table, 这个数组就是存放数据的. E

java编程中拷贝数组的方式及相关问题分析

JAVA数组的复制是引用传递,而并不是其他语言的值传递. 这里介绍java数组复制的4种方式极其问题: 第一种方式利用for循环: int[] a={1,2,4,6}; int length=a.length; int[] b=new int[length]; for (int i = 0; i < length; i++) { b[i]=a[i]; } 第二种方式直接赋值: int[] array1={1,2,4,6}; int[] array2=a; 这里把array1数组的值复制给arra

java理论基础Stream元素的匹配与查找

目录 一.对比一下有多简单 二.其他匹配规则函数介绍 三.元素查找与Optional 在我们对数组或者集合类进行操作的时候,经常会遇到这样的需求,比如: 是否包含某一个“匹配规则”的元素 是否所有的元素都符合某一个“匹配规则” 是否所有元素都不符合某一个“匹配规则” 查找第一个符合“匹配规则”的元素 查找任意一个符合“匹配规则”的元素 这些需求如果用for循环去写的话,还是比较麻烦的,需要使用到for循环和break!本节就介绍一个如何用Stream API来实现“查找与匹配”. 一.对比一下有