首页 > 技术文章 > jdk stream例子笔记,方便查阅

zhangyjblogs 2020-12-20 18:05 原文

前言

通常在开发中对于复杂的查询统计都使用stream,比如过滤、分组,但是stream使用有些复杂,以前专门学习了stream,但是实际使用中的时候还是忘记,对于稍微复杂点的使用(就像shell一样,遇到复杂点用法还得去查资料),还得查,因此总结了下stream的使用例子,便于使用的时候直接翻阅。

下面例子中有collectingAndThen的用法,这个用法个人感觉是比较难的,写过后还容易忘记这个用法。

List转List

List<T>转List<T>

//case1  list转list
public List<OpenServicePO> queryList() {
        List<OpenServicePO> openServicePOS = openServiceMapper.selectAll();//tkmybatis方法
        if (!CollectionUtils.isEmpty(openServicePOS)){
            openServicePOS = openServicePOS.stream().filter(openServicePO -> openServicePO.getIsShow()==1) .
            		sorted(Comparator.comparing(OpenServicePO::getUpdatedAt).reversed()).collect(Collectors.toList());
        }
        return openServicePOS;
    }
/*
*	openServicePOS.stream()获取stream
*	filter(openServicePO -> openServicePO.getIsShow()==1) 过滤isShow==1的元素,返回stream
*	Comparator.comparing(OpenServicePO::getUpdatedAt)按照updateAt更新日期排序,返回Comparator
*	reversed()翻转排序,返回Comparator	
*	sorted()传入Comparator
*	collect(Collectors.toList())把stream转换为list集合
*/

list<T>转List<R>

//把List<FeatureVO>转换为List<BugVO>
private OtherBranchRecordVO getBranchRecordVO(Integer id,List<Integer> productIdS,Integer roleId,Integer userId,Integer currentId,List<String> privileges) {
	    List<FeatureVO> featureVOS = getFeatureVOList(id, BRANCH_RECORD);//获取结果List<FeatureVO>
	        List<BugVO> bugMapperPOS = featureVOS.stream().filter(featureVO -> Objects.equals(JiraIssueTypeEnum.BUG.getJiraIssueType(), featureVO.getIssueType()))
	                .map(featureVO -> {
	                    BugVO bug = new BugVO();
	                    bug.setId(featureVO.getId());
	                    bug.setIssueSummary(featureVO.getIssueSummary());
	                    bug.setIssuePriority(featureVO.getIssuePriorityId());
	                    bug.setIssueStatus(featureVO.getIssueStatus());
	                    bug.setAssignee(featureVO.getAssignee());
	                    bug.setReporter(featureVO.getQatcName());
	                    bug.setIssuePriorityName(BugIssuePriorityEnum.getBugPriorityName(featureVO.getIssuePriorityId()));
	                    return bug;
	                }).collect(Collectors.toList());
	    //省略剩余代码
	}

另外例子,复杂转换,先转换为map,再转换为目标list<R>

//参考 https://juejin.im/entry/6844903462107365389
List<DeployRecordManageDetail> deployRecordManageDetails = deployRecordManageDetailMapper.selectDeployRecordByManageId(deployRecordId);//从数据库查询,deployRecordId是个Integer

Map<String, DeployRecordManageDetail> collect = deployRecordManageDetails.stream().collect(
    Collectors.groupingBy(DeployRecordManageDetail::getIp,	//按照ip分组
                          Collectors.collectingAndThen(
                              Collectors.maxBy(Comparator.comparingInt(DeployRecordManageDetail::getId)),//选择DeployRecordManageDetail表中该ip查询出的数据中id最大的数据
                              Optional::get //必须有这个,返回的是Optional对象
                          )
                         )
);//map的key是ip,value是每组内id最大的DeployRecordManageDetail对象
deployRecordManageDetails = collect.entrySet().stream().map(c -> c.getValue()).collect(Collectors.toList());//map的value返回,最终结果返回的是根据deployRecordId查询出的数据中,按照ip分组后,返回每组id最大者

List<T>转List<基本数据类型>

//转换结果map决定
List<Long> collect = featurePOS.stream()//featurePOS是个List<featurePO>
                        .filter(f -> !getStatus().contains(f.getStatus()))
                        .filter(f -> !Objects.equals(FeatureStatusEnum.CANCEL.getFeatureStatus(), f.getStatus()))
                        .map(FeaturePO::getId)
                        .collect(Collectors.toList());

同理转换为List<String>也是这样

获取集合中的某个属性转为集合

pictureList.stream().map(Picture::getSrc).collect(Collectors.toList());

根据集合中的某个属性进行升序重排

roomList.stream().sorted(Comparator.comparing(Room::getAvgPrice)).collect(Collectors.toList());

根据集合中的某个属性进行降序重排

roomList.stream().sorted(Comparator.comparing(Room::getAvgPrice).reversed()).collect(Collectors.toList());

集合中的属性去重

rpTags.stream().distinct().collect(Collectors.toList());

根据集合中的某个属性过滤并获取第一个

benefitList.stream().filter(benefit -> benefit.getId() == 1 || benefit.getId() == 20 || benefit.getId() == 26 || benefit.getId() == 89) .findFirst().orElse(null);

获取集合中某个最大值的int数据

partnerCityHotelDOList.stream().mapToInt(PartnerCityHotelDO::getId).max().orElse(-1);

List转Map

把实体对象的属性分别作为map的key value

public Map<Long, String> getIdNameMap(List<Account> accounts) {
    return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername));
}

收集成实体本身List<T>转Map<k,T>

public Map<Long, Account> getIdAccountMap(List<Account> accounts) {
    return accounts.stream().collect(Collectors.toMap(Account::getId, account -> account));
}

account -> account是一个返回本身的lambda表达式,其实还可以使用Function接口中的一个默认方法代替,使整个方法更简洁优雅:

public Map<Long, Account> getIdAccountMap(List<Account> accounts) {
    return accounts.stream().collect(Collectors.toMap(Account::getId, Function.identity()));
}

转换的map有重复key的情况

public Map<String, Account> getNameAccountMap(List<Account> accounts) {
    return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity()));
}

这个方法可能报错(java.lang.IllegalStateException: Duplicate key),因为username是有可能重复的。toMap有个重载方法,可以传入一个合并的函数来解决key冲突问题:

public Map<String, Account> getNameAccountMap(List<Account> accounts) {
    return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2));
}

这里只是简单的使用后者覆盖前者来解决key重复问题。

指定具体收集的map(返回特定类型的Map)

toMap还有另一个重载方法,可以指定一个Map的具体实现,来收集数据:

public Map<String, Account> getNameAccountMap(List<Account> accounts) {
    return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2, LinkedHashMap::new));
}

List<T>转Map<Integer, List<R>

//case2  list转map
	@Override
    public List<OpenTokenServiceWorkVo> queryList(Map<String,Object> map, String applyUser, String auditUser, String tokenName, String url, Integer status, Integer userId, Integer roleId, Integer pageNum, Integer pageSize){
        List<OpenTokenServiceWorkVo> openTokenServiceWorkVos = openServiceTokenMapper.queryByCondition(map);
        List<OpenTokenServiceWorkVo> openTokenServiceWorkVos1 = null;
        if (CollectionUtils.isNotEmpty(openTokenServiceWorkVos)){
            // 根据tokenId分组
        	/*
        	 * 把openTokenServiceWorkVos转换为Map<Integer, List<OpenServiceVO>>
        	 * Collectors.mapping() 先对集合中的元素进行映射,然后再对映射的结果使用Collectors操作,这里返回的就是list
        	 */
            Map<Integer, List<OpenServiceVO>> mapTemp = openTokenServiceWorkVos.stream().
                    collect(Collectors.groupingBy(b -> b.getId(),//b就是OpenTokenServiceWorkVo
                            Collectors.mapping(b -> new OpenServiceVO(b.getUrl(),b.getDescription()), Collectors.toList())));//先创建OpenServiceVO对象,mapping返回由Collectors.toList()决定,就是list
            // 根据tokenId去重
            openTokenServiceWorkVos1 = openTokenServiceWorkVos.stream().collect(
                    Collectors.collectingAndThen(Collectors.toCollection(
                            () -> new TreeSet<>(Comparator.comparing(o -> o.getId()))), ArrayList::new)//collectingAndThen是将流中的数据通过Collector计算,计算的结果再通过Function处理一下,返回的就是创建的ArrayList集合
            );
            if (CollectionUtils.isNotEmpty(openTokenServiceWorkVos1)){
                openTokenServiceWorkVos1 = openTokenServiceWorkVos1.stream().sorted(Comparator.comparing(OpenTokenServiceWorkVo::getUpdatedAt).reversed()).collect(Collectors.toList());
                openTokenServiceWorkVos1.stream().forEach(openTokenServiceWorkVo -> {
                    if (!org.springframework.util.CollectionUtils.isEmpty(mapTemp) && mapTemp.containsKey(openTokenServiceWorkVo.getId())){
                        openTokenServiceWorkVo.setOpenServiceVOS(mapTemp.get(openTokenServiceWorkVo.getId()));
                    }
                });
                if (CollectionUtils.isNotEmpty(openTokenServiceWorkVos1)){
                    openTokenServiceWorkVos1.stream().forEach(openTokenServiceWorkVo -> {
                        openTokenServiceWorkVo.setAuditButton(RoleEnum.ADMIN.getRoleId().equals(roleId)
                                && Objects.equals(OpenStatusEnum.TO_BE_AUDITED.getCode(),openTokenServiceWorkVo.getStatus()));
                        openTokenServiceWorkVo.setCancelButton(RoleEnum.ADMIN.getRoleId().equals(roleId)
                                && Objects.equals(OpenStatusEnum.EFFECTIVE.getCode(),openTokenServiceWorkVo.getStatus()));
                        openTokenServiceWorkVo.setQueryDetailButton(Objects.equals(userId,openTokenServiceWorkVo.getContactId()));
                    });
                }
            }
        }
        return openTokenServiceWorkVos1;
    }

以某个属性分组

Map<String,List<User>> map= userList.stream().collect(Collectors.groupingBy(User::getName));

list转换map时,把多个参数拼接作为key

Map<String, Parts> partsMap = synList.stream().collect(Collectors.toMap(k -> k.getOe()+k.getOeId()+k.getPartGroupId()+k.getStdPartId()+k.getBrandCode(), part -> part));//list转换map,其中key为几个属性的拼接,value就是每个Parts对象

List<OpsLabelDomainRelationPO> relationsList = opsLabelDomainRelationMapper.selectAll();
Map<String, OpsLabelDomainRelationPO> relationMap = relationsList.stream().collect(Collectors.toMap(ops -> 	ops.getDomainId()+"-"+ops.getOpsLabelId(), action -> action));//list转换map,其中key为几个属性的拼接,value就是每个po对象

Map转List<T>

把map中的key转换为集合

map.keySet().stream().collect(Collectors.toList());

把map中的value转换为集合

public static List<String> paramsToQueryString(Map<String, String> params) {
    return params.entrySet().stream().map(e -> e.getValue()).collect(Collectors.toList());
}

stream实际工作中在树形结构的应用

我们经常会有返回树形结构数据的需求。比如这里的权限,第一层是目录权限,目录权限之下有菜单权限,菜单权限之下有按钮权限。如果我们要返回一个集合,包含目录权限,目录权限下面嵌套菜单权限,菜单权限下嵌套按钮权限。使用Stream API可以很方便的解决这个问题。

注意:这里我们的权限上下级之间以pid来关联,pid是指上一级权限的id,顶级权限的id为0。

实例以权限UmsPermission为例,具体结构和数据如图

image-20210222221111070

创建与表对应的po对象

@Data
public class UmsPermission implements Serializable {
    private Long id;

    @ApiModelProperty(value = "父级权限id")
    private Long pid;

    @ApiModelProperty(value = "名称")
    private String name;

    @ApiModelProperty(value = "权限值")
    private String value;

    @ApiModelProperty(value = "图标")
    private String icon;

    @ApiModelProperty(value = "权限类型:0->目录;1->菜单;2->按钮(接口绑定权限)")
    private Integer type;

    @ApiModelProperty(value = "前端资源路径")
    private String uri;

    @ApiModelProperty(value = "启用状态;0->禁用;1->启用")
    private Integer status;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    @ApiModelProperty(value = "排序")
    private Integer sort;

    private static final long serialVersionUID = 1L;
}

定义包含下级权限的对象
继承自UmsPermission对象,之增加了一个children属性,用于存储下级权限。

public class UmsPermissionNode extends UmsPermission {
    private List<UmsPermissionNode> children;

    public List<UmsPermissionNode> getChildren() {
        return children;
    }

    public void setChildren(List<UmsPermissionNode> children) {
        this.children = children;
    }
}

定义获取树形结构的方法
我们先过滤出pid为0的顶级权限,然后给每个顶级权限设置其子级权限,covert方法的主要用途就是从所有权限中找出相应权限的子级权限。

@Override
public List<UmsPermissionNode> treeList() {
    List<UmsPermission> permissionList = permissionMapper.selectByExample(new UmsPermissionExample());
    List<UmsPermissionNode> result = permissionList.stream()
            .filter(permission -> permission.getPid().equals(0L))
            .map(permission -> covert(permission, permissionList)).collect(Collectors.toList());
    return result;
}

为每个权限设置子级权限
这里我们使用filter操作来过滤出每个权限的子级权限,由于子级权限下面可能还会有子级权限,这里我们使用递归来解决。但是递归操作什么时候停止,这里把递归调用方法放到了map操作中去,当没有子级权限时filter下的map操作便不会再执行,从而停止递归。

/**
* 将权限转换为带有子级的权限对象
* 当找不到子级权限的时候map操作不会再递归调用covert
*/
private UmsPermissionNode covert(UmsPermission permission, List<UmsPermission> permissionList) {
    UmsPermissionNode node = new UmsPermissionNode();
    BeanUtils.copyProperties(permission, node);
    List<UmsPermissionNode> children = permissionList.stream()
           .filter(subPermission -> subPermission.getPid().equals(permission.getId()))
           .map(subPermission -> covert(subPermission, permissionList)).collect(Collectors.toList());
    node.setChildren(children);
    return node;
}

在collectingAndThen用法中需要配合Optional,因此也记录下Optional用法

Optional用法例子

private static String getCharSet(Map<String, String> header) {
        return Optional.ofNullable(header).map(o -> o.get(CHAR_SET_HEADER_KEY)).orElse(CHAR_SET);//map的作用获取对象header中的具体对象,不使用map就是直接获取的是value,使用map可以获取value中的具体对象
    }
public static boolean getThrow404Exception() {
        return Optional.ofNullable(throw404Exception.get()).orElse(false);
    }
public static HttpConfigProperties getHttpConfigProperties() {
        return Optional.ofNullable(httpConfigProperties).orElseGet(()->new HttpConfigProperties());//httpConfigProperties非null返回httpConfigProperties对象,否则创建新的HttpConfigProperties对象
    }
ConsumerGroupMetadata metadata = Optional.ofNullable(metadata).orElseThrow(() -> ZmsException.MetainfoException);//metadata非null返回ConsumerGroupMetadata对象,否则抛出异常
private void handleBulkRequest(BulkRequest request){
        //有子请求
        if(Optional.ofNullable(request).map(o -> o.requests()).map(o -> o.size()).orElse(0) > 0){//map方法返回的还是个Optional对象
            //第一个请求的索引不带有正式或者测试的前缀,认定为用户直接调用的Bulk方法
            if(IndexUtils.hasPreFix(request.requests().get(0).index()) == false) {
                //根据线程上下文的影子标识,修改索引名称
                if(Zpt.shouldAsShadowOp()){
                    bulkRequestHandler.addPrefix(request);
                }
            }else{
                //否则,认识为 BulkProcessor 调用的此方法,由于BulkProcessor已经对索引加了前缀,要把Prod前缀去掉
                bulkRequestHandler.delProdPreFix(request);

            }
        }
    }

推荐阅读