如何将多个对象生成多叉树结构

  本文章主要描述创建树结构的构造思维
  创建泛型接口,以便获取到主键id、以及parentId值,以及parentList亲族链。

/**
 * <p> 树节点接口 </p>
 *
 */
public interface IEmTree<PK extends Serializable> {
 	
	/**
     * 获取主键id
     *
     * @return PK
     */
	PK getId();

    void setId(PK id);
	/**
     * 获取父节点id
     *
     * @return PK
     */
	PK getParentId();

    void setParentId(PK id);
    /**
     * 获取亲族链
     *
     * @return String
     */
    String getParentList();

    /**
     * 设置亲族链
     *
     * @param parentList 亲族链
     */
    void setParentList(String parentList);

    /**
     * 获取深度
     *
     * @return Integer
     */
    Integer getDepth();

    /**
     * 设置深度
     *
     * @param depth 深度
     */
    void setDepth(Integer depth);

    /**
     * 获取是否为叶子节点
     *
     * @return 是否是叶子节点[1为叶子节点,0为非叶子节点]
     */
    Integer getLeaf();

    /**
     * 设置是否为叶子节点
     *
     * @param leaf 是否是叶子节点[1为叶子节点,0为非叶子节点]
     */
    void setLeaf(Integer leaf);
}

  创建返回结构类型(拥有子集结构返回值)

public class AcceptanceTypeTreeNodeDTO extends AcceptanceTypeDTO {

  /**
   * 子集
   */
  @ApiModelProperty("子集")
  private List<AcceptanceTypeTreeNodeDTO> children;

  public AcceptanceTypeTreeNodeDTO(){
    children = Lists.newArrayList();
  }

  /**
   * 添加子集
   *
   * @param children 子集
   */
  public void addChildren(AcceptanceTypeTreeNodeDTO children){
    this.children.add(children);
  }

}

  保存树结构代码逻辑

public class EmTreeUtils {

    /**
     * 文件分组层级码分隔符
     */
    public static final String ELEMENT_FIRST_SEPARATOR = "-";

    /**
     * 空字符串
     */
    public static final String EMPTY = "";

    /**
     * 默认根节点parentList
     */
    public static final String DEFAULT_PARENT_LIST = "1-";
    /**
     * 文件分组顶级parentId
     */
    public static final Long TOP_PARENT_ID = 0L;
    /**
     * 文件分组顶级parentId
     */
    public static final Integer TOP_DEPTH = 1;
    /**
     * 文件分组顶级parentId
     */
    public static final Integer NOT_LEAF = 0;

    /**
     * 亲族链字段
     */
    private static final String P_COLUMN = "parent_list";

    private static final String PARENT_ID = "parent_id";
    /**
     * 新增或修改树节点
     *
     * @param param 参数
     * @param service 服务层接口
     * @param doSupplier DO Supplier
     * @param dtoSupplier DTO Supplier
     * @param <T> 参数类型
     * @param <DO> DO类型DTO
     * @param <DTO> DTO类型
     * @return
     */
    @SuppressWarnings("unchecked" )
    public static <T,DO extends IEmTree,DTO> DTO saveOrUpdate(T param, IService<DO> service,
                                                              Supplier<DO> doSupplier, Supplier<DTO> dtoSupplier) {
        // 转换对象类型(属性相同)
        DO typeDO = EsDoParser.param2Do(param,doSupplier);
        //设置默认为叶子节点
        typeDO.setLeaf(1);
        //处理层级及父级的亲族链
        String parentList = EMPTY;
        int depth = TOP_DEPTH;

        DO parent = null;
        if (!Objects.isNull(typeDO.getParentId()) && !TOP_PARENT_ID
                .equals(typeDO.getParentId())) {
            parent = service.getById(typeDO.getParentId());
            if (Objects.isNull(parent)) {
                throw EsBusinessExceptionWrapper.newEsBusinessException(EsErrorCode.USER_SAVE_FAIL,
                        String.format("未找到对应的父级,parentId:%s", typeDO.getParentId()));
            }
            if(!NOT_LEAF.equals(parent.getLeaf())){
                parent.setLeaf(NOT_LEAF);
                service.updateById(parent);
            }
            parentList = parent.getParentList();
            depth += parentList.split(ELEMENT_FIRST_SEPARATOR).length;
        } else {
            typeDO.setParentList(DEFAULT_PARENT_LIST);
            typeDO.setParentId(TOP_PARENT_ID);
        }
        //编辑时,入参不全时,空数据,不覆盖原有数据
        boolean isAdd = true;
        if (!Objects.isNull(typeDO.getId())) {
            if(typeDO.getId().equals(typeDO.getParentId())) {
                throw EsBusinessExceptionWrapper.newEsBusinessException(EsErrorCode.USER_SAVE_FAIL,"父级不应为自己");
            }
            isAdd = false;
            String newParentList = parentList + typeDO.getId() + ELEMENT_FIRST_SEPARATOR;

            //更新时修改子级亲族链(新父级不能为原子级)
            DO entity = service.getById(typeDO.getId());
            List<DO> children = service.list(
                    new QueryWrapper<DO>()
                            .likeRight(P_COLUMN, entity.getParentList()));
            if(!CollectionUtils.isEmpty(children)){
                children = children.stream().filter(e ->!entity.getId().equals(e.getId())).collect(Collectors.toList());
            }
            if(!CollectionUtils.isEmpty(children) && children.contains(parent)){
                throw EsBusinessExceptionWrapper.newEsBusinessException(EsErrorCode.USER_SAVE_FAIL,
                        String.format("父级不能为子级节点,parentId:%s", typeDO.getParentId()));
            }

            if(!CollectionUtils.isEmpty(children) && !children.contains(parent)){
                children.forEach(e ->{
                    e.setParentList(e.getParentList().replace(entity.getParentList(),newParentList));
                    e.setDepth(e.getParentList().split(ELEMENT_FIRST_SEPARATOR).length);
                });
            }
            service.updateBatchById(children);
            typeDO.setParentList(newParentList);
            int count = service.count(
                    new QueryWrapper<DO>()
                            .likeRight(P_COLUMN, typeDO.getParentList()));
            if (count > 1) {
                typeDO.setLeaf(NOT_LEAF);
            }

        }
        typeDO.setDepth(depth);

        //新增或保存
        BeanUtils.copyProperties(typeDO,param);
        service.saveOrUpdate(typeDO);
        DTO dto = EsDoParser.do2Dto(typeDO,dtoSupplier);

        //新增需更新 亲族链
        if (isAdd) {
            DO newDO = doSupplier.get();
            BeanUtils.copyProperties(dto, newDO);
            newDO.setParentList(parentList +
                    newDO.getId() + ELEMENT_FIRST_SEPARATOR);
            service.updateById(newDO);
            return EsDoParser.do2Dto(newDO, dtoSupplier);
        }
        return dto;
    }

    /**
     * 获取可以作为当前树节点的所有父节点列表
     * @param columnName 字段名
     * @param id 编辑树节点id
     * @param service 服务类
     * @param dtoSupplier DTO
     * @param <DO> DO
     * @param <S> id类型
     * @param <DTO> 返回值类型
     * @return 可以编辑为传入id节点的所有父节点
     */
    public static<DO extends IEmTree<Long>,DTO , S extends Serializable> List<DTO> getParent(String columnName, S id, IService<DO> service, Supplier<DTO> dtoSupplier) {
        if (Objects.isNull(id)) {
            return new ArrayList<>();
        }
        DO entity = service.getById(id);
        QueryWrapper<DO> queryWrapper = new QueryWrapper<>();
        queryWrapper.ne(columnName, id);
        List<DO> list = service.list(queryWrapper);
        List<DO> children = service.list(
                new QueryWrapper<DO>()
                        .likeRight(P_COLUMN, entity.getParentList()));
        if (CollectionUtils.isEmpty(children) || CollectionUtils.isEmpty(list)) {
            return EsDoParser.dos2DtoList(list, dtoSupplier);
        }
        List<Long> collect = children.stream().map(DO::getId).collect(Collectors.toList());
        List<DO> doList = list.stream().filter(e -> !collect.contains(e.getId())).collect(Collectors.toList());
        return EsDoParser.dos2DtoList(doList, dtoSupplier);
    }
}

  生成树工具类

public final class TreeUtils {

  private TreeUtils() {
  }

  public static class LinkedPair<P, C> {

    P p;
    C c;

    public LinkedPair(P p, C c) {
      this.p = p;
      this.c = c;
    }

    public C getC() {
      return c;
    }
  }

  /**
   * 建立树。
   * <p>请保证linker生成的结果中每个元素中的p与c同parents与children中元素hashcode相同(后续处理中会将p作为key生成map,并遍历parents中元素从map中获取值以生成最终结果),否则可能导致结果不正确</p>
   *
   * @param parents  上级
   * @param children 下级
   * @param linker   连接器。如何从上级与下级集合中,找到对应的上下级。
   * @param parser   P的转换器。并在此处将上级和下级关联(parent.addAll)
   * @param <P>      上级泛型
   * @param <C>      下级泛型
   * @param <NP>     最终结果泛型
   * @return 最终的上级集合
   */
  public static <P, C, NP> List<NP> buildTree(Collection<P> parents, Collection<C> children,
      BiFunction<Collection<P>, Collection<C>, Collection<LinkedPair<P, C>>> linker,
      BiFunction<P, List<C>, NP> parser) {
    Objects.requireNonNull(linker);
    Objects.requireNonNull(parser);
    if (parents == null) {
      return Collections.emptyList();
    }
    if (parents.isEmpty() || CollectionUtils.isEmpty(children)) {
      return parents.stream().map(item -> parser.apply(item, Collections.emptyList()))
          .collect(Collectors.toList());
    }
    Collection<LinkedPair<P, C>> apply = linker.apply(parents, children);
    Map<P, List<C>> collect = apply.stream()
        .collect(Collectors
            .groupingBy(item -> item.p, Collectors.mapping(LinkedPair::getC, Collectors.toList())));
    return parents.stream()
        .map(item -> parser.apply(item, collect.get(item)))
        .collect(Collectors.toList());
  }

  /**
   * 生成树。用于两种不同结构存在层级关系的时候。
   * <p/>
   * 如公司与部门之间
   *
   * @param parents     顶层数据
   * @param children    子孙数据
   * @param getPk       获取顶层数据 key
   * @param getParentId 获取子孙数据的上级的key
   * @param addChild    给顶层数据添加子节点
   * @param <P>         顶层数据泛型
   * @param <PK>        顶层数据key泛型,即子孙数据的parentId泛型
   * @param <C>         子孙数据泛型
   * @return 顶层数据列表
   */
  public static <P, PK, C> List<P> buildTree(List<P> parents, List<C> children,
      Function<P, PK> getPk, Function<C, PK> getParentId, BiConsumer<P, C> addChild) {
    if (parents == null
        || children == null) {
      return Collections.emptyList();
    }
    if (parents.isEmpty()) {
      return parents;
    }
    Objects.requireNonNull(getPk);
    Objects.requireNonNull(getParentId);
    Objects.requireNonNull(addChild);
    return buildTree(parents, children, (listP, listC) -> {
      List<LinkedPair<P, C>> result = new LinkedList<>();
      Map<PK, P> map = listP.stream().collect(Collectors.toMap(getPk, p -> p));
      listC.forEach(c -> {
        P p = map.get(getParentId.apply(c));
        if (Objects.nonNull(p)) {
          result.add(new LinkedPair<>(p, c));
        }
      });
      return result;
    }, (p, cList) -> {
      if (!CollectionUtils.isEmpty(cList)) {
        cList.forEach(item -> addChild.accept(p, item));
      }
      return p;
    });
  }


  /**
   * 生成树。自生成树。
   * <p/>
   * 如公司列表中,包含父公司与子公司
   * <p/>
   * 若parentId为null,则为顶层元素。若parentId不为null,但未找到父节点,则丢弃子节点。
   *
   * @param items       数据列表
   * @param getPk       获取id的方法
   * @param getParentId 获取上级id的方法
   * @param addChild    新增子节点的方法
   * @param <P>         数据泛型
   * @param <PK>        id泛型
   * @return 顶层节点列表
   */
  public static <P, PK> List<P> buildTree(List<P> items, Function<P, PK> getPk,
      Function<P, PK> getParentId, BiConsumer<P, P> addChild) {
    if (CollectionUtils.isEmpty(items)) {
      return items;
    }
    Objects.requireNonNull(getPk);
    Objects.requireNonNull(getParentId);
    Objects.requireNonNull(addChild);
    Set<PK> pkSet = items.stream().map(getPk).collect(Collectors.toSet());
    List<P> children = new LinkedList<>();
    List<P> top = new LinkedList<>();
    items.forEach(item -> {
      if (pkSet.contains(getParentId.apply(item))) {
        children.add(item);
      } else {
        top.add(item);
      }
    });
    List<P> result = buildTree(items, children, getPk, getParentId, addChild);
    return HmCollectionUtils.takeIntersectionByPk(getPk, top, result);
  }

  /**
   * 将树状结构实体转换为新的扁平的list
   *
   * @param toFlat        待转换的树状实体
   * @param subListGetter 获取某个树状实体的下级实体列表的方法
   * @param <T>           实体类型
   * @return 树状结构的扁平数据列表
   */
  public static <T> List<T> flatToList(List<T> toFlat, Function<T, List<T>> subListGetter) {
    Objects.requireNonNull(subListGetter);
    if (toFlat == null) {
      return Collections.emptyList();
    }
    List<T> result = new ArrayList<>(toFlat);
    List<T> tmpResult = result;
    while (true) {
      tmpResult = tmpResult.stream().flatMap(item -> subListGetter.apply(item).stream())
          .collect(Collectors.toList());
      if (CollectionUtils.isEmpty(tmpResult)) {
        break;
      }
      result.addAll(tmpResult);
    }
    return result;
  }

  /**
   * 是否顶层节点
   *
   * @param parent 父级节点id
   * @param <PK>   主键泛型
   * @return true 是顶层节点, false 不是顶层节点
   */
  public static <PK extends Serializable> boolean isTop(PK parent) {
    if (parent instanceof String) {
      Long parentLong = NumberParser.tryParseLong((String) parent);
      return Objects.equals(parentLong, ParentListTreeConstant.PARENT_OF_TOP_LEVEL_LONG);
    } else {
      return Objects.equals(parent, ParentListTreeConstant.PARENT_OF_TOP_LEVEL_LONG);
    }
  }

  /**
   * 拼接 levelCode
   *
   * @param parentLevelCode 上级节点的 levelCode
   * @param id              自己的主键
   * @param <PK>            主键泛型
   * @return 自己的 levelCode
   */
  public static <PK extends Serializable> String concatLevelCode(String parentLevelCode,
      PK id) {
    return parentLevelCode + id + ParentListTreeConstant.LEVEL_CODE_SPLITTER;
  }

}

HmCollectionUtils

public final class HmCollectionUtils {

  private HmCollectionUtils() {}

  /**
   * 将容器中的元素指定field收集到新容器
   *
   * @param entities 元素容器
   * @param fieldGetter field提供器
   * @param <EN> 元素泛型
   * @param <FD> field泛型
   * @return field_collection
   */
  public static <EN, FD> Set<FD> collectFields(
      Collection<EN> entities, Function<EN, FD> fieldGetter) {
    return (Set<FD>) collectFields(entities, fieldGetter, HashSet::new);
  }

  /**
   * 将容器中的元素指定field收集到新容器(去重)
   *
   * @param entities 元素容器
   * @param fieldGetter field提供器
   * @param <EN> 元素泛型
   * @param <FD> field泛型
   * @return field_collection
   */
  public static <EN, FD> Collection<FD> collectFields(
      Collection<EN> entities, Function<EN, FD> fieldGetter, Supplier<Collection<FD>> supplier) {
    Objects.requireNonNull(fieldGetter);
    if (CollectionUtils.isEmpty(entities)) {
      return supplier.get();
    }
    return entities.stream().map(fieldGetter).collect(Collectors.toCollection(supplier));
  }

  /**
   * 根据指定字段查找两个容器内的数据交集
   *
   * @param pkGetter pkGetter
   * @param c1 c1
   * @param c2 返回集合基础集合。在c2中去除c1中不包含的,再返回c2副本
   * @param <E> 实体泛型
   * @param <PK> ID泛型
   * @return 以c2为基础的交集数据
   */
  public static <E, PK> List<E> takeIntersectionByPk(
      Function<E, PK> pkGetter, Collection<E> c1, Collection<E> c2) {
    return takeIntersectionByPkReturnSectionCollectionItems(c1, pkGetter, c2, pkGetter);
  }

  /**
   * 通过指定字段判定,查找不同数据类型中的数据交集,返回第二个集合中的交集数据
   *
   * @param pkCollection collection1
   * @param pkGetter1 pkGetter
   * @param entityCollection 返回集合基础集合。在entityCollection中去除pkCollection中不包含的,再返回c2副本
   * @param pkGetter2 pkGetter
   * @param <E> 实体泛型1
   * @param <G> 实体泛型2
   * @param <PK> 主键泛型
   */
  public static <E, G, PK> List<G> takeIntersectionByPkReturnSectionCollectionItems(
      Collection<E> pkCollection,
      Function<E, PK> pkGetter1,
      Collection<G> entityCollection,
      Function<G, PK> pkGetter2) {
    if (CollectionUtils.isEmpty(pkCollection) || CollectionUtils.isEmpty(entityCollection)) {
      return Collections.emptyList();
    }
    Set<PK> pksOfC1 = pkCollection.stream().map(pkGetter1).collect(Collectors.toSet());
    return entityCollection.stream()
        .filter(m -> pksOfC1.contains(pkGetter2.apply(m)))
        .collect(Collectors.toList());
  }

  /**
   * @param entities 集合
   * @param keyGetter key
   * @param valueGetter value
   * @param <EN> 集合元素类型
   * @param <K> 键类型
   * @param <V> 值类型
   * @return 结果集map
   * @author guxu
   */
  public static <EN, K, V> Map<K, V> collectFieldsToMap(
      Collection<EN> entities, Function<EN, K> keyGetter, Function<EN, V> valueGetter) {
    Objects.requireNonNull(keyGetter);
    Objects.requireNonNull(valueGetter);
    if (CollectionUtils.isEmpty(entities)) {
      return Collections.emptyMap();
    }
    return entities.stream().collect(Collectors.toMap(keyGetter, valueGetter, (a, b) -> a));
  }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值