列表转树结构,以及树结构转列表应该怎么做?有没有通用的方法?

单纯列表转树的逻辑我是清楚的,不管用什么实现方案,核心应该都是根据 id 找到父节点,并将当前节点添加到父节点的子节点列表属性中。

我的问题是在java 中能不能写一个通用的实现?

比如:只有一个资源实体类需要转树时,那直接写一个与资源实体类相对应的节点类,这个节点类不仅有资源实体类的所有属性,而且还多一个子节点列表属性就可以了。但如果有多个需要转换树结构的资源呢?总不能给每一个需要转换的资源实体类都创建对应的节点类吧?而且即便是只有一个,也是有弊端的,如果实体类有几百个属性,那这几百个属性还要挨个重写一遍?

我现在想到了两种方案,但各有弊端,具体如下:
第一:利用泛型,建一个通用的 TreeNode,就像这样:

public class TreeNode<T>  {
    private T PO;
    private List<TreeNode> childList;
    ....
}

这样就是将实体类对象用泛型保存,子节点列表在外面单独用一个属性保存,不管有多少各类需要转换,也不管需要被转换的类型里有多少属性都可以用,但这样的话,子节点列表就和其他属性割裂开了,对前端或其他树状结构数据消费者可能是不友好。
第二:利用继承,将包含 childList 的 TreeNode 作为所有需要实现列表转树的父类。但这样的弊端就更明显了,比如有一个需要转换树节点的类已经继承了其他类怎么办?而且很多甚至大部分需要转换的都是实体类,而实体上是没有childList 的,这样做虽然没有直接改实体类,但是在父类上加了childList,其对象也是会有childList 属性的,那这还能算是一个实体对象吗是不是有点不合适?

目前就想到以上两种方案,但是都不尽人意,项目上大概率不会采用。希望有大佬提供更成熟的方案指点一二,不甚感激。

阅读 1.7k
3 个回答
✓ 已被采纳

你都想到继承了,那为啥不多考虑一下,试试接口?
再一个咱们反射不能用啊?

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;

public class TreeTest {

public static void main(String[] args) throws Exception {

    List<User> users = new ArrayList<>();
    users.add(new User("1","湖南省",null));
    users.add(new User("2","广东省",null));
    users.add(new User("3","长沙市","1"));
    users.add(new User("4","深圳市","2"));
    users.add(new User("5","岳麓区","3"));
    users.add(new User("6","望城坡街道","5"));
    users.add(new User("7","湘潭市","1"));
    List<? extends TreeNode> tree = tree(users);

    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println(objectMapper.writeValueAsString(tree));
}


public static <T extends TreeNode> List<T> tree(List<T> list){
    List<T> headerNodes = new ArrayList<>();
    // 构造顶级节点
    for (TreeNode tree : list) {
        if(StringUtils.isEmpty(tree.getPid())){
            headerNodes.add((T) tree.newInstance());
        }
    }
    // 循环将当前节点加入到父节点的集合中
    for (T t : list) {
        parentNodeAddChild(t,headerNodes);
    }
    return headerNodes;
}


private static <T extends TreeNode>  void parentNodeAddChild(TreeNode<T> node,List<T> parentNodes){
    for (TreeNode treeNode : parentNodes) {
        // 找到父节点,直接加入子集并且结束循环
        if(StringUtils.equals(node.getPid(),treeNode.getId())){
            treeNode.getChildList().add(node);
            break;
        }
        // 递归循环父节点
        parentNodeAddChild(node, treeNode.getChildList());
    }
}

}

interface TreeNode<T>{

String getPid();

String getId();

List<T> getChildList();

String getText();

<T extends TreeNode> T newInstance();

}

@Data
@JsonIgnoreProperties({"name","children"})
class User implements TreeNode<User>{

public User(String id, String name, String pid) {
    this.id = id;
    this.name = name;
    this.pid = pid;
    this.children = new ArrayList<>();
}

@Override
public String getPid() {
    return pid;
}

@Override
public List<User> getChildList() {
    return children;
}

@Override
public String getText() {
    return name;
}

@Override
public User newInstance() {
    return new User(id,name,pid);
}

@Override
public String toString() {
    return "User{" +
            "id='" + id + '\'' +
            ", getText()='" + name + '\'' +
            ", pid='" + pid + '\'' +
            ", children=" + children +
            '}';
}

private String id;

private String name;

private String pid;

private List<User> children;

}

写一个泛型函数,类型就是T ,然后参数有获取父id.获取id,设置children这些

   public TreeUtils(Function<T, String> parentIdFunction, Function<T, String> idFunction,
                     BiConsumer<T, List<T>> setChildrenFunction, Function<T, List<T>> getChildrenFunction) {
        this.parentIdFunction = parentIdFunction;
        this.idFunction = idFunction;
        this.setChildrenFunction = setChildrenFunction;
        this.getChildrenFunction = getChildrenFunction;
    }

大体这个意思,然后你再写个函数,扔个list,去处理就行,反正处理tree的本质就是塞children

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏