过滤

使用谓词过滤

filter方法接受一个谓词函数为参数,并返回包含所有匹配谓词的元素的流。如下例:

1
List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());

如下图所示:

filter.png

过滤唯一元素

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);

如下图所示:

distinct.png

切分流

Java 9添加了两个新方法takeWhiledropWhile,可以有效地选择流中的元素。

takeWhile

takeWhile方法使用谓词切分流,即使是无限流。一旦发现一个元素不满足条件,takeWhile会立刻停止。

1
List<Dish> slicedMenu1 = specialMenu.stream().takeWhile(dish -> dish.getCalories() < 320).collect(toList());

dropWhile

dropWhiletakeWhile正好相反。

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>,这样不对。

如下图所示:

first-try.png

尝试使用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());

flatMapmap(Arrays::stream)产生的一组流合并为一个流。

如下图所示:

flatmap.png

查找和匹配

匹配至少一个元素

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);

anyMatchallMatch noneMatch 都具有短路求值的特性。

查找一个元素

findAny返回流中任意一个元素。

1
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();

查找第一个元素

findFirstfindAny类似,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.png

没有初始值

还有一个不带初始值的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种基础类型的特殊流:IntStreamDoubleStreamLongStream。这些流提供了很多方便用于数值计算的方法,比如summaxminaverage

映射为数字流

将一个流转换为数字流的最常用的方法是使用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类型:OptionalIntOptionalDoubleOptionalLong

1
2
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
int max = maxCalories.orElse(1);

数字范围

IntStreamLongStream提供2个静态方法rangerangeClosed用于生成数字范围。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.iterateStream.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);