java8 Stream API

java8 Stream API

基本概念

为什么引进了Stream?

众所周知,集合操作非常麻烦,若要对集合进行筛选、投影,需要写大量的代码,而stream是以声明的形式操作集合,它就像SQL语句我们只需要告诉流需要对集合进行什么操作,它就会自动进行操作,并将执行结果交给你,无需我们自己手写代码。

什么是stream?

官方文档上给的解释是:支持顺序和并行聚合操作的一系列元素。其实,stream是java8引入的一个重度使用lambda表达式的API。stream是采用内部迭代方式,若要对集合进行处理,则需我们手写处理代码,这就叫外部迭代。而要对流进行处理,我们只需要告诉流我们需要什么结果,处理过程由流自行完成,这就叫内部迭代。

基本操作

获取流

1:集合
1
2
List<Person> list = new ArrayList<Person>(); 
Stream<Person> stream = list.stream();
2:数组
1
2
String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);
3:数值
1
Stream<String> stream = Stream.of("chaimm","peter","john");

方法详细信息及demo

筛选filter

filter函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为true的元素。

注:Person::isStudent代表Person().isStudent()

1
2
3
List<Person> result = list.stream()
.filter(Person::isStudent)
.collect(toList());
映射map

对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
如,获取每个人的姓名(实则是将泛型约束为Perosn类型转换成泛型约束为String类型):

1
2
3
4
List<Person> personList = new ArrayList<>();
List<String> result = personList.stream()
.map(Person::getName)
.collect(toList());
去重distinct
1
2
3
List<Person> result = list.stream()
.distinct()
.collect(toList());
截取limit

截取流的前N个元素:

1
2
3
List<Person> result = list.stream()
.limit(3)
.collect(toList());
跳过skip

跳过流的前n个元素

1
2
3
List<Person> result = list.stream()
.skip(3)
.collect(toList());
迭代forEach

forEach用于迭代流中的每个数据

如,输出十个随机数

1
2
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
排序sorted

sorted用于对流进行排序

1
2
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
Collectors

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

1
2
3
4
5
6
7
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//筛选列表
List<String> filtered = strings.stream()
.filter(string -> !string.isEmpty()).collect(Collectors.toList());
//合并字符串
String mergedString = strings.stream()
.filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
是否匹配任一元素anyMatch

anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断list中是否有学生:

1
2
boolean result = list.stream()
.anyMatch(Person::isStudent);
是否匹配所有元素allMatch

allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断是否所有人都是学生:

1
2
boolean result = list.stream()
.allMatch(Person::isStudent);
是否未匹配所有元素noneMatch

noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:

1
2
boolean result = list.stream()
.noneMatch(Person::isStudent);
Stream并行流

默认情况下我们使用几个的Stream方法创建的是一个串行流,你有两种办法让他变成并行流

  1. 调用Stream对象的parallel方法
  2. 创建流的时候调用parallelStream而不是stream方法

以下实例我们使用 parallelStream 来输出空字符串的数量:

1
2
3
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();
获取任一元素findAny

findAny能够从流中随便选一个元素出来,它返回一个Optional类型的元素。

1
2
Optional<Person> person = list.stream()
.findAny();

Optional介绍

Optional是Java8新加入的一个容器,这个容器只存1个或0个元素,它用于防止出现NullpointException,它提供如下方法:

  • isPresent()
    判断容器中是否有值。
  • ifPresent(Consume lambda)
    容器若不为空则执行括号中的Lambda表达式。
  • T get()
    获取容器中的元素,若容器为空则抛出NoSuchElement异常。
  • T orElse(T other)
    获取容器中的元素,若容器为空则返回括号中的默认值。
获取第一个元素findFirst
1
2
Optional<Person> person = list.stream()
.findFirst();

归约

归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。

在流中,reduce函数能实现归约。
reduce函数接收两个参数:

  • 初始值
  • 进行归约操作的Lambda表达式
元素求和:自定义Lambda表达式实现求和

例:计算所有人的年龄总和

1
int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge());1

reduce的第一个参数表示初试值为0;
reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,reduce会把流中的元素两两输给Lambda表达式,最后将计算出累加之和。

元素求和:使用Integer.sum函数求和

上面的方法中我们自己定义了Lambda表达式实现求和运算,如果当前流的元素为数值类型,那么可以使用Integer提供了sum函数代替自定义的Lambda表达式,如:

1
int age = list.stream().reduce(0, Integer::sum);1

Integer类还提供了min、max等一系列数值操作,当流中元素为数值类型时可以直接使用。

数值流的使用

采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低。
当流操作为纯数值操作时,使用数值流能获得较高的效率。

将普通流转换成数值流

StreamAPI提供了三种数值流:IntStream、DoubleStream、LongStream,也提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong。
如,将Person中的age转换成数值流:

1
2
IntStream stream = list.stream()
.mapToInt(Person::getAge);12
数值计算

每种数值流都提供了数值计算函数,如max、min、sum等。
如,找出最大的年龄:

1
2
3
OptionalInt maxAge = list.stream()
.mapToInt(Person::getAge)
.max();123

由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理。
此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong