Java8 Lambda map()和flatMap()
在使用lambda的map时候看到了flatMap这个方法,之前知道和map不一样,但是一直没管它,这次又想起来,就找了点资料看了下。
References:
- https://mkyong.com/java8/java-8-flatmap-example/ 大佬的文章非常好, 强烈建议看看
- https://www.baeldung.com/java-difference-map-and-flatmap
- https://stackoverflow.com/questions/26684562/whats-the-difference-between-map-and-flatmap-methods-in-java-8
- https://www.cnblogs.com/lijingran/p/8727507.html
首先看下面的一段数据结构:
# 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的flatMap
将Stream<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