java8映射:map和flatMap的正确打开方式

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 excels转化成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对象的流,并对其倒叙排序,所以无法获取我们期望的结果。下图说明了这个问题:
java8映射:map和flatMap的正确打开方式_第1张图片

解决方案:使用flatMap

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。下图说明了这个问题:
java8映射:map和flatMap的正确打开方式_第2张图片
最终返回结果为:

{
    "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实战》

你可能感兴趣的:(java基础随笔)