java8映射:map和flatMap的正确打开方式
- 映射
- 案例
- 解决方案:使用flatMap
如今java8已经成为各大互联网及软件开发公司中的标配了,java8中也有很多新的功能和特性能够提高我们的开发效率,使代码从复杂到简单再到优雅。而map和flatMap也是我们平时开发过程比较常用的API,但是在实际运开发中有些小伙伴会把二者混淆,今天笔者根据自身在日常开发中的使用经验做一下总结。
映射
这里引用《java8实战中》中的概念:“使用映射一词,是因为它和转换类型类似,但其中的细微差别在于它是创建一个新版本而不是去修改”。流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射到新的元素上。废话不多说我们上案例。
案例
我们知道一个Excel表格里面可以新建多个sheet页。
定义Excel类和sheet类,一个Excel中有多个sheet,数据结构如下:
@Data
class Excel {
/**
* sheet页面集合.
*/
private List<Sheet> sheetList;
}
@Data
class Sheet {
/**
* sheet页的序号.
*/
private Integer index;
}
初始化excels:
@Test
public void mapTest() throws JsonProcessingException {
List<Excel> excels = Lists.newArrayList();
//初始化exces值
initExcelList(excels);
System.out.println(new ObjectMapper().writeValueAsString(excels));
}
/**
* 这只是个初始化值的demo,可以忽略.
* @param excels
*/
private void initExcelList(List<Excel> excels) {
//创建三个ExcelDemo
int val = 0;
for (int i = 1;i <=3; i++) {
List<Sheet> sheets = Lists.newArrayList();
//每个Demo里添加四个Sheet
for(int j = 1;j <= 4; j++) {
sheets.add(new Sheet().setIndex(val + j));
}
excels.add(new Excel().setSheetList(sheets));
val += 4;
}
}
获取到excels的数据如下:
[
{
"sheetList":[
{
"index":1
},
{
"index":2
},
{
"index":3
},
{
"index":4
}
]
},
{
"sheetList":[
{
"index":5
},
{
"index":6
},
{
"index":7
},
{
"index":8
}
]
},
{
"sheetList":[
{
"index":9
},
{
"index":10
},
{
"index":11
},
{
"index":12
}
]
}
]
现在假设有个需求要求我们将这些Excel合并成一个Excel并且对sheet页进行倒叙展示。首先我们想到通过stream().map()
方法将List<Excel> excels
转化成Stream<List<Sheet>>
然后将Stream<List<Sheet>>
转化成Stream<Sheet>
进行倒叙排序后通过收集器collect(Collectors.toList())
收集成一个List<Sheet>
后赋值给单个Excel
对象。具体操作如下:
@Test
public void mapTest() throws JsonProcessingException {
List<Excel> excels = Lists.newArrayList();
//初始化exces值
initExcelList(excels);
//合并excels
Excel excel = mergeExcel(excels);
System.out.println(new ObjectMapper().writeValueAsString(excel));
}
private Excel mergeExcel(List<Excel> excels) {
//将excels合并成一个Excel,并且将sheet按照序号的倒叙排列
List<Stream<Sheet>> collect = excels.stream()
.map(Excel::getSheetList)
.map(List::stream)
.collect(Collectors.toList());
collect.sort(Comparator.reverseOrder());//编译错误
return null;
}
这个方法的问题在于,传递给map方法的map(Excel::getSheetList)
为每一个Excel
返回了一个List<Sheet>
。因此map返回的流实际上是Stream<List<Sheet>>
。继续调用map(List::stream)
方法为每一个List<Sheet>
返回了一个Stream<Sheet>
,返回的流实际上是Stream<Stream<Sheet>>
。而我们想要的是一个Stream<Sheet>
来表示一个Sheet对象的流,并对其倒叙排序,所以无法获取我们期望的结果。下图说明了这个问题:
解决方案:使用flatMap
flatMap会接受一个函数作为参数,这个函数的返回值是另一个流。这个方法会应用到流中的每一个元素,最终形成一个新的流的流,即当调用map()
方法时会产生一个嵌套流Stream<Stream<Obj>>
时,此时把map()
方法替换成flatMap()
方法会把Stream<Stream<Obj>>
的每一个流合并为单一的流Stream<Obj>
。具体操作如下:
private Excel mergeExcel(List<Excel> excels) {
//将excels合并成一个Excel,并且将sheet按照序号的倒叙排列
List<Sheet> sheets = excels.stream()
.map(Excel::getSheetList)
.flatMap(List::stream)
.sorted(Comparator.comparing(Sheet::getIndex).reversed())
.collect(Collectors.toList();
return new Excel().setSheetList(sheets));
}
当我们调用flatMap()
时,此时返回不是Stream<Stream<Sheet>>
而是合并之后的Stream<Sheet>
。下图说明了这个问题:
最终返回结果为:
{
"sheetList":[
{
"index":12
},
{
"index":11
},
{
"index":10
},
{
"index":9
},
{
"index":8
},
{
"index":7
},
{
"index":6
},
{
"index":5
},
{
"index":4
},
{
"index":3
},
{
"index":2
},
{
"index":1
}
]
}
除了Stream
中的flatMap()
方法之外,Optional
中也支持此方法。功能和过程都是一样的,都是由方法生产的个个流会被合并或者扁平化为一个单一的流,你可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。这里不举例说明,有兴趣的小伙伴可以自己验证一下。
- 参考《java8实战》