java8 Stream API
基本概念
为什么引进了Stream?
众所周知,集合操作非常麻烦,若要对集合进行筛选、投影,需要写大量的代码,而stream是以声明的形式操作集合,它就像SQL语句我们只需要告诉流需要对集合进行什么操作,它就会自动进行操作,并将执行结果交给你,无需我们自己手写代码。
什么是stream?
官方文档上给的解释是:支持顺序和并行聚合操作的一系列元素。其实,stream是java8引入的一个重度使用lambda表达式的API。stream是采用内部迭代方式,若要对集合进行处理,则需我们手写处理代码,这就叫外部迭代。而要对流进行处理,我们只需要告诉流我们需要什么结果,处理过程由流自行完成,这就叫内部迭代。
基本操作
获取流
1:集合
1 | List<Person> list = new ArrayList<Person>(); |
2:数组
1 | String[] names = {"chaimm","peter","john"}; |
3:数值
1 | Stream<String> stream = Stream.of("chaimm","peter","john"); |
方法详细信息及demo
筛选filter
filter函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为true的元素。
注:Person::isStudent代表Person().isStudent()
1 | List<Person> result = list.stream() |
映射map
对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
如,获取每个人的姓名(实则是将泛型约束为Perosn类型转换成泛型约束为String类型):
1 | List<Person> personList = new ArrayList<>(); |
去重distinct
1 | List<Person> result = list.stream() |
截取limit
截取流的前N个元素:
1 | List<Person> result = list.stream() |
跳过skip
跳过流的前n个元素
1 | List<Person> result = list.stream() |
迭代forEach
forEach用于迭代流中的每个数据
如,输出十个随机数
1 | Random random = new Random(); |
排序sorted
sorted用于对流进行排序
1 | Random random = new Random(); |
Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
1 | List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); |
是否匹配任一元素anyMatch
anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断list中是否有学生:
1 | boolean result = list.stream() |
是否匹配所有元素allMatch
allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断是否所有人都是学生:
1 | boolean result = list.stream() |
是否未匹配所有元素noneMatch
noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:
1 | boolean result = list.stream() |
Stream并行流
默认情况下我们使用几个的Stream方法创建的是一个串行流,你有两种办法让他变成并行流
- 调用Stream对象的parallel方法
- 创建流的时候调用parallelStream而不是stream方法
以下实例我们使用 parallelStream 来输出空字符串的数量:
1 | List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); |
获取任一元素findAny
findAny能够从流中随便选一个元素出来,它返回一个Optional类型的元素。
1 | Optional<Person> person = list.stream() |
Optional介绍
Optional是Java8新加入的一个容器,这个容器只存1个或0个元素,它用于防止出现NullpointException,它提供如下方法:
- isPresent()
判断容器中是否有值。 - ifPresent(Consume lambda)
容器若不为空则执行括号中的Lambda表达式。 - T get()
获取容器中的元素,若容器为空则抛出NoSuchElement异常。 - T orElse(T other)
获取容器中的元素,若容器为空则返回括号中的默认值。
获取第一个元素findFirst
1 | Optional<Person> person = list.stream() |
归约
归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。
在流中,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 | IntStream stream = list.stream() |
数值计算
每种数值流都提供了数值计算函数,如max、min、sum等。
如,找出最大的年龄:
1 | OptionalInt maxAge = list.stream() |
由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理。
此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong