在Stream流中添加中间操作
将一个Stream流映射到另一个Stream流
使用map()
方法将一个stream映射为另一个stream,参数为Function
。使用Function
对stream中的元素进行处理。
映射可以改变元素类型,也可以不改变元素类型
List<String> strings = List.of("one", "two", "three", "four");
Function<String, Integer> toLength = String::length;
Stream<Integer> ints = strings.stream()
.map(toLength);
由于没有添加终端操作,这段代码不做任何事情,并不处理任何数据。
添加一个终端操作collect(Collectors.toList())
处理数据到一个列表中:
List<String> strings = List.of("one", "two", "three", "four");
List<Integer> lengths = strings.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("lengths = " + lengths);
运行次代码输出:
lengths = [3, 3, 5, 4]
有三种方法可以从 Stream
转到原始类型的流:mapToInt()
、mapToLong()
和 mapToDouble()
。
List<String> strings = List.of("one", "two", "three", "four");
IntSummaryStatistics stats = strings.stream()
.mapToInt(String::length)
.summaryStatistics();
System.out.println("stats = " + stats);
输出结果:
stats = IntSummaryStatistics{count=4, sum=15, min=3, average=3,750000, max=5}
过滤stream流
过滤就是通过predicate谓词(断言)丢弃一些stream流中的元素。过滤可用于stream流中的对象或原始数据类型。
假设你需要计算长度为3的字符串的个数
List<String> strings = List.of("one", "two", "three", "four");
long count = strings.stream()
.map(String::length)
.filter(length -> length == 3)
.count();
System.out.println("count = " + count);
输出结果
count = 2
注意count()
为终端操作,计算出流中已处理的元素个数。
flatMap()
用来处理一对多的关系
public class City {
private String name;
private int population;
// constructors, getters
// toString, equals and hashCode
}
public class State {
private String name;
private List<City> cities;
// constructors, getters
// toString, equals and hashCode
}
计算所有城市的人口
List<State> states = ...;
int totalPopulation = 0;
for (State state: states) {
for (City city: state.getCities()) {
totalPopulation += city.getPopulation();
}
}
System.out.println("Total population = " + totalPopulation);
内部循环代码是映射-归约形式
List<State> states = ...;
int totalPopulation = 0;
for (State state: states) {
totalPopulation += state.getCities().stream().mapToInt(City::getPopulation).sum();
}
System.out.println("Total population = " + totalPopulation);
flatmap操作打开对象之间一对多的关系,并创建stream流。flatMap()
方法接收一个特殊的参数Function<? super T,? extends Stream<? extends R>>
返回Stream
对象。类之间的关系由该Function
定义。
List<State> states = ...;
int totalPopulation =
states.stream()
.flatMap(state -> state.getCities().stream())
.mapToInt(City::getPopulation)
.sum();
System.out.println("Total population = " + totalPopulation);
flatMap()
分2步处理stream流:
- 通过
Function
映射stream流中的所有元素,从Stream<State>
创建Stream<Stream<City>>
- flattening(展平,压扁)
Stream<Stream<City>>
使其变为单个Stream<City>
使用flatMap()
和mapMulti()
(jdk16以上)验证元素转换
flatMap()
可以验证stream流中元素的转换。
假设你有一个字符串流,字符串代表数字。你需要使用中间操作Integer.parseInt()
转换。但是如果字符串为空字符串、null、有额外的空格等,解析会抛出NumberFormatException
异常。当然你可以先过滤掉异常等字符串。
使用过滤并不是一个很好的方法
Predicate<String> isANumber = s -> {
try {
int i = Integer.parseInt(s);
return true;
} catch (NumberFormatException e) {
return false;
}
};
一个的缺陷是首先你需要实际的过滤操作,然后在使用同样的函数进行中间映射操作。另一个缺陷是在catch
代码块中返回并不是一个好的编码习惯。
正确的做法应该是可以解析的直接返回,不能解析的什么都不返回。可以使用flatMap()
,可以解析的返回一个解析结果stream流,不能解析的返回一个空的stream流。
Function<String, Stream<Integer>> flatParser = s -> {
try {
return Stream.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
}
return Stream.empty();
};
List<String> strings = List.of("1", " ", "2", "3 ", "", "3");
List<Integer> ints =
strings.stream()
.flatMap(flatParser)
.collect(Collectors.toList());
System.out.println("ints = " + ints);
运行结果
ints = [1, 2, 3]
使用flatMap()
可以很好的工作,但也有一定的开销:需要为stream流中的每一个元素创建stream流。jdk16添加了mapMulti()
Stream API。mapMulti()
需要BiConsumer
参数。
BiConsumer
有2个参数:
- 需要映射的stream流中的元素
- 此
BiConsumer
需要使用映射结果调用的Consumer
调用consumer将元素添加到结果stream流中。如果不调用consumer则元素不会被添加到stream流中。
List<Integer> ints =
strings.stream()
.<Integer>mapMulti((string, consumer) -> {
try {
consumer.accept(Integer.parseInt(string));
} catch (NumberFormatException ignored) {
}
})
.collect(Collectors.toList());
System.out.println("ints = " + ints);
运行结果,但不会添加额外的stream流了
ints = [1, 2, 3]
删除重复项并对流进行排序
distinct()
使用元素的hashCode()
和equals()
去重。
sorted()
使用元素的Comparator
或提供Comparator
进行排序。
distinct()
可以用于未绑定(无限)stream流,但sorted()
不行。
限制和跳过stream流的元素
Stream API 为您提供了两种选择流元素的方法:
- 基于索引
Predicate
谓词(断言)
skip()
和limit()
基于索引,它们都需要一个long
类型参数。
List<Integer> ints = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> result =
ints.stream()
.skip(2)
.limit(5)
.collect(Collectors.toList());
System.out.println("result = " + result);
结果
result = [3, 4, 5, 6, 7]
jdk9添加了2个方法代替基于索引的skip()
和limit()
,新方法基于Predicate
谓词(断言)。
dropWhile(predicate)
丢弃stream流中的元素直到Predicate
返回true
takeWhile(predicate)
恰恰相反:它将元素传输到下一个流,直到Predicate
返回false
。
这2个方法像门一样工作,dropWhile()
打开门,不再关闭。takeWhile()
关闭门,不在打开。
连接stream流
Stream API 提供了几种模式来将多个流连接成一个。最常使用的是concat()
,该方法只能连接2个stream流,如果要连接2个以上的stream流,建议使用flatMap()
List<Integer> list0 = List.of(1, 2, 3);
List<Integer> list1 = List.of(4, 5, 6);
List<Integer> list2 = List.of(7, 8, 9);
// 1st pattern: concat
List<Integer> concat =
Stream.concat(list0.stream(), list1.stream())
.collect(Collectors.toList());
// 2nd pattern: flatMap
List<Integer> flatMap =
Stream.of(list0.stream(), list1.stream(), list2.stream())
.flatMap(Function.identity())
.collect(Collectors.toList());
System.out.println("concat = " + concat);
System.out.println("flatMap = " + flatMap);
结果
concat = [1, 2, 3, 4, 5, 6]
flatMap = [1, 2, 3, 4, 5, 6, 7, 8, 9]
使用 flatMap()
方式更好的原因是 concat()
在连接期间会创建中间流。当您使用 Stream.concat()
时,会创建一个新流来连接您的两个流。
使用flatMap()
模式,您只需创建一个Stream<Stream<T>>
流来保存所有stream流并执行flatMap()
。开销要低得多。
concat()
产生SIZED
stream流,而flatMap()
不行。
调试stream流
peek()
List<String> strings = List.of("one", "two", "three", "four");
List<String> result =
strings.stream()
.peek(s -> System.out.println("Starting with = " + s))
.filter(s -> s.startsWith("t"))
.peek(s -> System.out.println("Filtered = " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("Mapped = " + s))
.collect(Collectors.toList());
System.out.println("result = " + result);
结果
Starting with = one
Starting with = two
Filtered = two
Mapped = TWO
Starting with = three
Filtered = three
Mapped = THREE
Starting with = four
result = [TWO, THREE]