前言
通常在开发中对于复杂的查询统计都使用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为例,具体结构和数据如图
创建与表对应的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);
}
}
}