Java8 Lambda map()和flatMap()

在使用lambda的map时候看到了flatMap这个方法,之前知道和map不一样,但是一直没管它,这次又想起来,就找了点资料看了下。
Snipaste_2021-01-07_13-27-49.png

References:

首先看下面的一段数据结构:

# Stream<String[]>
# Stream<Stream<String>>
# String[][]

[
  [1, 2],
  [3, 4],
  [5, 6]
]

可以把它看成是一个两层嵌套的Stream,或者一个二维数组。那么分别map和flatMap对这样结构的数据进行处理会有什么不同?

String[][] arrs = {
    {"1", "2"},
    {"3", "4"},
    {"5", "6"}
};

List<String> strList = Stream.of(arrs)
    .map(Arrays::toString)
    .collect(Collectors.toList());
System.out.println(strList);
// [[1, 2], [3, 4], [5, 6]]

List<String> strFlatList = Stream.of(arrs)
    .flatMap(Stream::of)
    .collect(Collectors.toList());
System.out.println(strFlatList);
// [1, 2, 3, 4, 5, 6]

处理超过一层的Stream是有挑战性的,像Stream<String[]>或者Stream<List<LineItem>>或者Stream<Stream<String>>这样的。flatMap会将2层Stream展开成一层Stream,这样可以更容易的循环Stream并且处理其中的元素。

下面是一个二维数组,现在要过滤掉“a”元素,并且打印其余的元素。

String[][] array = new String[][]{{"a", "b"}, {"c", "d"}, {"e", "f"}};

// array to a stream
Stream<String[]> stream2 = Stream.of(array);

首先,尝试直接使用Stream#filter, 程序会将所有元素都打印出来, 因为在filter中, equals比较的是String[]和"a", 所以equals总是false, filter返回的总是true, 并不会过滤任何元素。

String[][] array = new String[][]{{"a", "b"}, {"c", "d"}, {"e", "f"}};

// convert array to a stream
Stream<String[]> stream1 = Arrays.stream(array);

List<String[]> result = stream1
  .filter(x -> !x.equals("a"))      // x is a String[], not String!
  .collect(Collectors.toList());

System.out.println(result.size());    // 3
result.forEach(x -> System.out.println(Arrays.toString(x)));
// [a, b]
// [c, d]
// [e, f]

好吧,这次重写filter方法来处理String[]:

String[][] array = new String[][]{{"a", "b"}, {"c", "d"}, {"e", "f"}};

// array to a stream
Stream<String[]> stream1 = Arrays.stream(array);

// x is a String[]
List<String[]> result = stream1
      .filter(x -> {
          for(String s : x){      // really?
              if(s.equals("a")){
                  return false;
              }
          }
          return true;
      }).collect(Collectors.toList());

// print array
result.forEach(x -> System.out.println(Arrays.toString(x)));
// [c, d]
// [e, f]

在上面的例子中, Stream#filter会过滤掉整个[a, b], 但是我们想只过滤掉a。下面是最终版, 首先将数据结合起来, 再跟着一个过滤器。在Java中, 将2维数组转为1维数组, 可以循环二维数组然后将所有元素放到一个新数组中, 或者使用Java8的flatMapStream<String[]>转为Stream<String>

String[][] array = new String[][]{{"a", "b"}, {"c", "d"}, {"e", "f"}};

List<String> collect = Stream.of(array)   // Stream<String[]>
      .flatMap(Stream::of)                // Stream<String>
      .filter(x -> !"a".equals(x))        // filter out the a
      .collect(Collectors.toList());      // return a List

collect.forEach(System.out::println);

我想指出的是, 处理超过1层的Stream是有挑战性的, 混乱的和容易出错的, 我们可以使用Stream#flatMap将2层Stream展开成1层Stream。

Stream<String[]>      -> flatMap ->    Stream<String>
Stream<Set<String>>   -> flatMap ->    Stream<String>
Stream<List<String>>  -> flatMap ->    Stream<String>
Stream<List<Object>>  -> flatMap ->    Stream<Object>

最后再一个栗子吧, 将List转为Stream, 每个对象包含一个book的Set集合, 使用flatMap生成一个包含所有book的Stream。最后,过滤掉包含“python”的书, 然后转为Set去重:

package com.mkyong.java8.stream.flatmap;

import java.util.HashSet;
import java.util.Set;

public class Developer {

    private Integer id;
    private String name;
    private Set<String> book;

    //getters, setters, toString

    public void addBook(String book) {
        if (this.book == null) {
            this.book = new HashSet<>();
        }
        this.book.add(book);
    }
}
package com.mkyong.java8.stream.flatmap;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class FlatMapExample1 {

    public static void main(String[] args) {

        Developer o1 = new Developer();
        o1.setName("mkyong");
        o1.addBook("Java 8 in Action");
        o1.addBook("Spring Boot in Action");
        o1.addBook("Effective Java (3nd Edition)");

        Developer o2 = new Developer();
        o2.setName("zilap");
        o2.addBook("Learning Python, 5th Edition");
        o2.addBook("Effective Java (3nd Edition)");

        List<Developer> list = new ArrayList<>();
        list.add(o1);
        list.add(o2);

        // hmm....Set of Set...how to process?
        /*Set<Set<String>> collect = list.stream()
                .map(x -> x.getBook())
                .collect(Collectors.toSet());*/

        Set<String> collect =
                list.stream()
                        .map(x -> x.getBook())                              //  Stream<Set<String>>
                        .flatMap(x -> x.stream())                           //  Stream<String>
                        .filter(x -> !x.toLowerCase().contains("python"))   //  filter python book
                        .collect(Collectors.toSet());                       //  remove duplicated

        collect.forEach(System.out::println);
    }
}

// Output:
Spring Boot in Action
Effective Java (3nd Edition)
Java 8 in Action

标签: none

添加新评论

ali-01.gifali-58.gifali-09.gifali-23.gifali-04.gifali-46.gifali-57.gifali-22.gifali-38.gifali-13.gifali-10.gifali-34.gifali-06.gifali-37.gifali-42.gifali-35.gifali-12.gifali-30.gifali-16.gifali-54.gifali-55.gifali-59.gif

加载中……