Java8 stream流操作重构递归代码

原代码:

 private static JSONArray produceTree(List<Resource> resources,boolean needPermission){
    JSONArray tree = new JSONArray();
    if(resources!=null&&resources.size()>Number.ZERO) {//递归退出条件判断
        for (Resource resource : resources) {
            List<Resource> resourceList=resourceService.queryChildTreeByPid(resource.getId());//根据父id查询子资源列表
            JSONArray children = produceTree(resourceList,needPermission,staffName,roleId);//递归,一直向下查找
            JSONObject node=TreeUtil.spellTree(resource);//转换为具体的树形式
            if(children!=null&&children.size()>Number.ZERO){
                node.put("children",children);
            }else if(needPermission){//需要该资源下对应的权限信息(最后一级,判断下面有没有权限关系)
                node=toPermission(resource.getId(),node,staffName);
            }
            tree.add(node);
        }
    }
    return tree;
}

Resource结构如下:

        @Data
        public class Resource {
        private Integer id;//资源id
    
        private String resourceName;//资源名称
    
        private String url;//资源地址 可能无用
    
        private String icon;//资源图标
    
        private String clue;// 树形结构横向线索
    
        private Integer distance; // 到根节点的距离 纵向线索
    
        private Integer parentId;// 父资源id
    
        private String description; //资源描述
    
        private Date createtime;//创建时间
    
        private Integer state;// 0未删除 1已删除
        
        List<Resource> children;
    
    }

现在想用java8 Stream流重写这段代码,大概是这样:

        private static List<Resource> recursive(List<Resource> resources){
            return resources.stream()
                    .map(Resource::getId)//第一步转换为id
                    .map(parentId->resourceService.queryChildTreeByPid(parentId))//第二步查找id下对应的资源(id相当于parentId使用)
                    .map(child->Optional.ofNullable(child).filter(obj -> true).ifPresent(isExists->recursive(isExists)))//第三步 如果childrenList不为null,递归此方法
                    .toArray(childrenL->reReousce.setChildren());//第四步 将得到的List<Resource> 收集到上一个resource的children中 , resource.setChildren(childrenList)
        }

第三步以后 编译出错
设想一下 第三步应该用toArray方法收集子资源 然后第四步将收集到的子资源放进对应的父资源resource中 请问如何实现呢

--------------感谢imanguo大佬的指点 还有某me姓大佬的指点 分享下我的树形结构生产源码-------
--------------Any library suggestions/code samples/guidance would be greatly appreciated...--------------

private static ResourceService resourceService;
private static PermissionService permissionService;

@Autowired
public TreeUtil(ResourceService resourceService,PermissionService permissionService){
    this.resourceService=resourceService;
    this.permissionService=permissionService;
}
/**
 * @Author: zms
 * @Description: 生成菜单树结构
 *
 * clue是线索 例如 1-2-3 代表 根节点是1 1下面有2节点 2下面有3节点
 * 根据收集根节点id 作为线索 可以去除持久化数据里面重复的资源
 * 例如 同时有了 1节点 2节点 因为1是2的父节点 那么有了1就不需要2的存在了 有父节点就拥有其所有子节点
 *
 * @Date: Create on  2018/11/12 14:59
 */
public static List<Resource> produceTree(List<Resource> resources,boolean needPermission){
    Map<Boolean,List<Resource>> listMap=partitionResource(resources);
    List<Resource> nodeTree=listMap.get(true);//根节点资源树
    List<Resource> duplicateChildTree=listMap.get(false);//非根节点
    String clue=nodeTree.stream().map(Resource::getId).map(String::valueOf).collect(joining("-","","-"));//根节点线索
    List<Resource> childTree=duplicateChildTree.stream().filter(resource -> !clue.contains(resource.getClue())).collect(toList());//去除重复资源
    nodeTree.addAll(childTree);//合并所有
    List<Resource> downTree=TreeUtil.recursiveDown(nodeTree,needPermission);//向下递归查找子节点
    List<Resource> upTree=TreeUtil.recursiveUp(downTree);//向上递归查找父节点
    return merge(upTree);
}
/**
 * @Author: zms
 * @Description: 新递归生成树方法 上至下
 *
 *                   5 ---
 *            2 ---  6 ---
 *       1--- 3 ---  7 ---
 *            4 ---  8 ---
 *                   9 ---
 *
 *  以1开始查找到2,3,4 然后2,3,4再次递归 2(5,6) 3(7) 4(8,9)  括号内是对应的children
 *  为什么引入ResourceGroup? 将resource和ChildrenResource 向上抽象一层
 *  Resource中含有其他属性 ResourceGroup仅仅表示父子节点的关系 可以不使用 看个人习惯
 * @Date: Create on  2018/11/8 11:30
 */
public static List<Resource> recursiveDown(List<Resource> resources, boolean needPermission){
    return resources
            .parallelStream()
            .map(resource -> TreeUtil.toResourceGroup(resource,needPermission))
            .peek(ResourceGroup::autoSet)
            .map(ResourceGroup::getParent)
            .collect(Collectors.toList());
}
/**
 * @Author: zms
 * @Description: 转换成resouceGroup收集
 * @Date: Create on  2018/11/8 11:30
 */
private static ResourceGroup toResourceGroup(Resource resource, boolean needPermission) {
    ResourceGroup resourceGroup=new ResourceGroup();
    resourceGroup.setParent(resource);
    List<Resource> children = findChildren(resource.getId(),needPermission);
    if(Objects.equals(0,children.size())&&needPermission){
        List<Permission> permissions=permissionService.getPermissionByResourceId(resource.getId());
        resourceGroup.setPermissions(permissions);
        return resourceGroup;
    }
    resourceGroup.setChildren(children);
    return resourceGroup;
}
/**
 * @Author: zms
 * @Description: 获取父节点对应的子资源
 * @Date: Create on  2018/11/8 11:30
 */
private static List<Resource> findChildren(Integer parentId,boolean needPermission){
    List<Resource> chidren=resourceService.queryChildTreeByPid(parentId);
    recursiveDown(chidren,needPermission);
    return chidren;
}
/**
 * @Author: zms
 * @Description: 新递归生成树方法 下至上
 * @Date: Create on  2018/11/8 18:32
 */
private static List<Resource> recursiveUp(List<Resource> resources){
    return resources.stream()
                    .map(TreeUtil::toResource)
                    .map(ResourceGroup::getParent)
                    .collect(Collectors.toList());
}

/**
 *@Author: zms
 *@Description: 递归查找父节点
 *@Date: Create On 2018/11/10 14:33
 */
private static ResourceGroup toResource(Resource resource){
    ResourceGroup resourceGroup=new ResourceGroup();
    Resource parent=findParent(resource);
    resourceGroup.setParent(parent);
    return resourceGroup;
}
/**
 * @Author: zms
 * @Description: 获取收集好的子资源的父节点
 * @Date: Create on  2018/11/8 18:37
 */
private static Resource findParent(Resource resource){
    if(Objects.equals(0,resource.getParentId())){
        return resource;
    }
    Resource parent=resourceService.getResourceById(resource.getParentId());
    List<Resource> children=new ArrayList<>();
    children.add(resource);
    parent.setChildren(children);
    return recursiveUp(Collections.singletonList(parent)).stream().filter(Objects::nonNull).findFirst().orElse(resource);
}
/**
 * @Author: zms
 * @Description:
 *
 *  业务背景: 假设id为1的资源节点下有 2,3,4 三个子资源  可能某个角色只有 2,3的资源树
 *  2 -->1
 *  dulipcate
 *  3 -->1
 *  这时需要把2 3 合并到 1 下
 * @Date: Create on  2018/11/12 11:44
 */
private static List<Resource> merge(List<Resource> duplicateTree){
    return duplicateTree
            .stream()
            .collect(toMap(Resource::getId, Function.identity(),TreeUtil::mergeChildResource))
            .entrySet()
            .stream()
            .map(Map.Entry::getValue)
            .collect(Collectors.toList());
}

/**
 * @Author: zms
 * @Description:
 *
 *  id:1                     id:1                      id:1
 *  children:[               children:[                children:[
 *          {                        {                         {
 *              id=2                    id=3    合并为            id=2
 *          }                        }                         },
 *  ]                        ]                                 {
 *                                                               id=3
 *                                                             }
 *
 * @Date: Create on  2018/11/12 12:00
 */
private static Resource mergeChildResource(Resource resource1,Resource resource2){
    resource1.getChildren().add(resource2.getChildren().get(0));
    return resource1;
}

/**
 * @Author: zms
 * @Description: 根节点与不是根节点的resource分组
 * @Date: Create on  2018/11/12 15:01
 */
private static Map<Boolean,List<Resource>> partitionResource(List<Resource> resources){
    return resources.stream().collect(partitioningBy(resource-> Objects.equals(0,resource.getParentId())));
}
/**
 * @Author: zms
 * @Description: 去重方法
 * @Date: Create on  2018/8/10 10:13
 */

public static String removeDuplicateString(String str){
    return Stream.of(str.split(",")).distinct().collect(Collectors.joining(","));
}
阅读 7.9k
2 个回答

emmm,我拿下来了代码,不过编译有各种错误,我就没有管源代码的事,所以这个回答可能最终无法给你直接可以使用的答案代码,只有一个思路了哈

根据你最后提问这个,结合你代码,简单看了哈你想要达到的效果

clipboard.png

应该是,有一堆节点List<Resource>,遍历它们,根据其中的id属性作为条件查询父属性parentId值与之相当的子节点List<Resource>,最后可能要对子节点做一些处理后再放到刚父节点的children属性

整个过程看来,个人感觉一个比较肯定的答案是:这个过程不能通过一套的stream完成,也就是不能通过你写的这种一套完成下去

clipboard.png

为啥这么说呢,stream本身要处理的场景也就是不断的数据转换映射和过滤的过程,stream里包含的数据也是在这个过程中不断变化的,所以这里有个关键点就是,本身按照父Resource是能转换成子List<Resource>,但是子List<Resource>再次映射成其他的时候,stream里的数据已经不再有父Resource了,所以你不能再用之前stream里的数据再来做set动作

结合你的代码再来看看(写stream操作的时候,一定随时要注意stream里当前的数据到底是什么,才不会写错)

resources.stream()

此时根据resources生成流,此时是Stream<Resource>

.map(Resource::getId)

用了map操作,这就是映射转换操作,此时已经变成Stream<Long>,其实这一步已经意味着你之后不能再用Resource.setChildren了,因为此时流里已经是Long,是其资源id了,后面就算能用id再转换成子节点List<Resource>也无力回天了

当然并不是说没法弄了,明白了stream的这些性质,也还是可以写的,不过最终写法可能用不到太多stream语法

因为最终是需要再用到之前的父Resource的,所以要保留之前的父Resource,所以可以尝试把循环父Resource和找到其子Resource,并最终set回父Resource,这三个过程分开写

private static List<Resource> recursive(List<Resource> resources){
        resources.forEach(resource -> addChildren(resource));
        return resources;
    }

    private static void addChildren(Resource resource) {
        // 根据id查询其子Resource
        List<Resource> children = findChildren(resource.getId());
        // 子Resource被设置到父resource
        resource.setChildren(children);
    }

    private static List<Resource> findChildren(Integer id) {
        List<Resource> children = resourceService.queryChildTreeByPid(id);
        recursive(children);
        return children;
    }

这只是按照你后面写的代码来改造的写法,有两点

  1. 这个写法可能没有你想象中需要用到的stream的写法,不过业务决定代码(不强制套任何结构或者框架,找到属于业务最合适,最清楚的代码就是最好的)
  2. 这个写法个人感觉跟你源代码差别很大,当然因为就是按照你最后写的方式改造的,源代码那里缺少代码太多,要做什么不理清楚,可能对最终的写法造成影响

以上仅供参考哈

===================================小做更改==================================

根据刚才的评论,想起之前不知道谁说的话:引用新的编程元素可以更加简单整理业务流程

这里新增一个ResourceGroup类来确定父与子之间的关系

@Data
public class ResourceGroup {

    private Resource parent;
    private List<Resource> children;

    public ResourceGroup autoSet(){
        this.parent.setChildren(this.children);
        return this;
    }
}

里面属性也就是一个父Resource 和子List<Resource>,这哈就可以直接用stream完全表示了

private static List<Resource> recursive(List<Resource> resources){
        List<Resource> newResources = resources.stream()
                                               .map(Test::toResourceGroup)
                                               .peek(ResourceGroup::autoSet)
                                               .map(ResourceGroup::getParent)
                                               .collect(Collectors.toList());
        return newResources;
    }

多了一个toResourceGroup方法

private static ResourceGroup toResourceGroup(Resource resource) {
        ResourceGroup resourceGroup = new ResourceGroup();

        resourceGroup.setParent(resource);

        List<Resource> children = findChildren(resource.getId());
        resourceGroup.setChildren(children);

        return resourceGroup;
    }

其他的就是之前的方法,这样就可以在stream中补你还需要处理的方法了

private static List<Resource> recursive(List<Resource> resources){
    return resources.stream()
            .map(Resource::getId)//第一步转换为id
            .map(parentId->resourceService.queryChildTreeByPid(parentId))//第二步查找id下对应的资源(id相当于parentId使用)
            .filter(obj -> obj != null)//第三步 如果childrenList不为null,递归此方法
            .toArray(childrenL->reReousce.setChildren());//第四步 将得到的List<Resource> 收集到上一个resource的children中 , resource.setChildren(childrenList)
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进