这里引用《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
转化成Stream
然后将>
Stream
转化成>
Stream
进行倒叙排序后通过收集器collect(Collectors.toList())
收集成一个List
后赋值给单个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
。因此map返回的流实际上是Stream
。继续调用>
map(List::stream)
方法为每一个List
返回了一个Stream
,返回的流实际上是Stream
。而我们想要的是一个Stream
来表示一个Sheet对象的流,并对其倒叙排序,所以无法获取我们期望的结果。下图说明了这个问题:
flatMap会接受一个函数作为参数,这个函数的返回值是另一个流。这个方法会应用到流中的每一个元素,最终形成一个新的流的流,即当调用map()
方法时会产生一个嵌套流Stream
时,此时把map()
方法替换成flatMap()
方法会把Stream
的每一个流合并为单一的流Stream
。具体操作如下:
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
。下图说明了这个问题:
最终返回结果为:
{
"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对象看成一种特殊的集合数据,它至多包含一个元素。这里不举例说明,有兴趣的小伙伴可以自己验证一下。