过滤
使用谓词过滤
filter
方法接受一个谓词函数为参数,并返回包含所有匹配谓词的元素的流。如下例:
1
|
List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());
|
如下图所示:
过滤唯一元素
distinct
方法返回包含唯一元素的流。如下例:
1
2
|
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
|
如下图所示:
切分流
Java 9添加了两个新方法takeWhile
和dropWhile
,可以有效地选择流中的元素。
takeWhile
takeWhile
方法使用谓词切分流,即使是无限流。一旦发现一个元素不满足条件,takeWhile
会立刻停止。
1
|
List<Dish> slicedMenu1 = specialMenu.stream().takeWhile(dish -> dish.getCalories() < 320).collect(toList());
|
dropWhile
dropWhile
和takeWhile
正好相反。
1
|
List<Dish> slicedMenu2 = specialMenu.stream().dropWhile(dish -> dish.getCalories() < 320).collect(toList());
|
截断流
limit(n)
方法返回一个不大于指定长度的流
1
|
List<Dish> dishes = specialMenu.stream().filter(dish -> dish.getCalories() > 300).limit(3).collect(toList());
|
跳过元素
skip(n)
方法返回一个丢弃前n个元素的流,如果流中的元素小于n,则返回一个空流
1
|
List<Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());
|
映射
map
map
方法接受一个Function<T, R>
为参数,将其应用于每一个元素,并映射为一个新的元素
1
|
List<Integer> dishNameLengths = menu.stream().map(Dish::getName).map(String::length).collect(toList());
|
flatMap
如何返回一组单词中所有唯一的字符呢?比如[“Hello”, “World”],你想要返回[“H”, “e”, “l”, “o”, “W”, “r”, “d”] 。第一次尝试如下:
1
|
words.stream().map(word -> word.split("")).distinct().collect(toList());
|
map
返回的流为Stream<String[]>
,而你想要的流是Stream<String>
,这样不对。
如下图所示:
尝试使用map和Arrays.stream
首先你需要一个字符串流而不是数组流。Arrays.stream
方法接受一个数组并产生一个包含数组元素的流:
1
2
|
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
|
第二次尝试:
1
|
words.stream().map(word -> word.split("")).map(Arrays::stream).distinct().collect(toList());
|
这个方法依然不行,因为map(Arrays::stream)
返回一组流,准确的说是List<Stream<String>>
。
使用flatMap
可以使用flatMap
解决这个问题:
1
|
List<String> uniqueCharacters = words.stream().map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(toList());
|
flatMap
将map(Arrays::stream)
产生的一组流合并为一个流。
如下图所示:
查找和匹配
匹配至少一个元素
anyMatch
用于查找至少一个匹配给定谓词,返回boolean
1
2
3
|
if (menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
|
匹配所有元素
allMatch
检查是否所有元素匹配给定谓词,返回boolean
1
|
boolean isHealthy = menu.stream().allMatch(dish -> dish.getCalories() < 1000);
|
不匹配任何元素
noneMatch
所有元素都不满足给定谓词,返回boolean
1
|
boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);
|
anyMatch
,allMatch
和noneMatch
都具有短路求值的特性。
查找一个元素
findAny
返回流中任意一个元素。
1
|
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
|
查找第一个元素
findFirst
和findAny
类似,findFirst
查找第一个元素
1
2
|
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream().map(n -> n * n).filter(n -> n % 3 == 0).findFirst();
|
findFirst在并行处理的时候有更多的限制,如果你不介意返回哪个元素,并行处理的时候选择findAny
归纳
计算元素的和
使用reduce
计算流中所有元素的和:
1
|
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
|
reduce
接受2个参数:
- 一个初始值
- 一个
BinaryOperator<T>
计算2个元素的值并产生一个新值
你可以使用方法引用使得代码更简明:
1
|
int sum = numbers.stream().reduce(0, Integer::sum);
|
如下图所示:
没有初始值
还有一个不带初始值的reduce
重载版本,它返回一个Optional
对象
1
|
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
|
查找最大和最小元素
1
2
|
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
|
流API总结表:
操作 |
类型 |
返回类型 |
参数(类型/函数接口) |
函数描述符 |
filter |
中间操作 |
Stream<T> |
Predicate<T> |
T -> boolean |
distinct |
中间操作(有状态-无上界) |
Stream<T> |
|
|
takeWhile |
中间操作 |
Stream<T> |
Predicate<T> |
T -> boolean |
dropWhile |
中间操作 |
Stream<T> |
Predicate<T> |
T -> boolean |
skip |
中间操作(有状态-有上界) |
Stream<T> |
long |
|
limit |
中间操作(有状态-有上界) |
Stream<T> |
long |
|
map |
中间操作 |
Stream<R> |
Function<T, R> |
T -> R |
flatMap |
中间操作 |
Stream<R> |
Function<T, Stream<R>> |
T -> Stream<R> |
sorted |
中间操作(有状态-无上界) |
Stream<T> |
Comparator<T> |
(T, T) -> int |
anyMatch |
结束操作 |
boolean |
Predicate<T> |
T -> boolean |
noneMatch |
结束操作 |
boolean |
Predicate<T> |
T -> boolean |
allMatch |
结束操作 |
boolean |
Predicate<T> |
T -> boolean |
findAny |
结束操作 |
Optional<T> |
|
|
findFirst |
结束操作 |
Optional<T> |
|
|
forEach |
结束操作 |
void |
Consumer<T> |
T -> void |
collect |
结束操作 |
R |
Collector<T, A, R> |
|
reduce |
结束操作(有状态-有上界) |
Optional<T> |
BinaryOperator<T> |
(T, T) -> T |
count |
结束操作 |
long |
|
|
数字流
原始类型的专有流
Java8提供3种基础类型的特殊流:IntStream
,DoubleStream
和LongStream
。这些流提供了很多方便用于数值计算的方法,比如sum
,max
,min
,average
映射为数字流
将一个流转换为数字流的最常用的方法是使用mapToInt
,mapToDouble
和mapToLong
方法
1
|
int calories = menu.stream().mapToInt(Dish::getCalories).sum();
|
转换回对象流
使用boxed
方法将数字流转换为对象流
1
2
|
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
|
默认值
数字流有3个特殊版本的Optinal
类型:OptionalInt
,OptionalDouble
和OptionalLong
1
2
|
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
int max = maxCalories.orElse(1);
|
数字范围
IntStream
和LongStream
提供2个静态方法range
和rangeClosed
用于生成数字范围。range
的范围是[start, end),rangeClosed
的范围是[start, end]。
1
|
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
|
数字流实例
查找毕氏三元数(勾股数)
1
2
3
|
Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100).boxed().flatMap(a -> IntStream.rangeClosed(a, 100).filter(b -> Math.sqrt(a*a + b*b) % 1 == 0).mapToObj(b ->
new int[]{a, b, (int)Math.sqrt(a * a + b * b)}));
pythagoreanTriples.limit(5).forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
|
第二种方法
1
2
|
Stream<double[]> pythagoreanTriples2 = IntStream.rangeClosed(1, 100).boxed().flatMap(a ->
IntStream.rangeClosed(a, 100).mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)}).filter(t -> t[2] % 1 == 0));
|
创建流
从一组值创建流
Stream.of
接受任意个数参数并返回一个流
1
2
|
Stream<String> stream = Stream.of("Modern ", "Java ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
|
Stream.empty
返回一个空流
从可空元素创建流
Stream.ofNullable
可以从可空对象创建流
1
2
3
|
String homeValue = System.getProperty("home");
Stream<String> homeValueStream = homeValue == null ? Stream.empty() : Stream.of(value);
Stream<String> homeValueStream = Stream.ofNullable(System.getProperty("home"));
|
从数组创建流
Arrays.stream
可以从数组创建流
1
2
|
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
|
从文件创建流
java的NIO提供了许多静态方法返回流,比如Files.lines
1
2
3
4
5
|
long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(""))).distinct().count();
} catch(IOException e){
}
|
从函数创建流
Stream.iterate
和Stream.generate
可以让你创建一个无限流,通常需要结合limit
使用
iterate
iterate
接受2个参数:
- 初始值
UnaryOperator<T>
用于迭代产生新值
1
|
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
|
在Java9中,iterate
被增强了,支持谓词。
1
|
IntStream.iterate(0, n -> n < 100, n -> n + 4).forEach(System.out::println);
|
相当于
1
|
IntStream.iterate(0, n -> n + 4).takeWhile(n -> n < 100).forEach(System.out::println);
|
generate
generate
接受一个Supplier<T>
参数来产生新值
1
|
Stream.generate(Math::random).limit(5).forEach(System.out::println);
|
使用generate
生成前20个Fibonacci序列数字:
1
2
3
4
5
6
7
8
9
10
11
12
|
IntSupplier fib = new IntSupplier() {
private int previous = 0;
private int current = 1;
public int getAsInt(){
int oldPrevious = this.previous;
int nextValue = this.previous + this.current;
this.previous = this.current;
this.current = nextValue;
return oldPrevious;
}
};
IntStream.generate(fib).limit(20).forEach(System.out::println);
|