上一篇中介绍了集合操作的痛点并发现集合操作的这些痛点在java8流API面前基本都不是事,随后引出了流的定义并介绍了流操作的类型、特征,以及使用流的基本步骤,本篇将逐一介绍Stream Api中各种流操作及一些特殊流的使用。
关于筛选在该系列前面文章中已经出现多次了,这里再次列举如下,以求完整。
如下代码筛选出素食菜单:
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)//方法引用检查是否为素食
.collect(toList());
去重是指去除筛选结果中的重复项,只需在fiter之后调用distinct()即可:
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
截短流是指从流中截取部分数据,如取出热量大于300卡路里的前3道菜:
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)//截短流
.collect(toList());
跳过元素是指跳过流中指定数量的元素,对剩下的进行操作或取出,如下面的代码将跳过大于300卡路里的头3道菜:
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(3)//跳过前3个元素
.collect(toList());
当菜单流中热量大于300卡路里的菜不足3个时,上面的代码返回一个空的List<Dish>
。
流的映射是指对流的每个元素应用一个指定的函数,使其映射成我们需要的新的元素。如从菜单中映射过菜名列表,代码如下:
List<String> dishNames = menu.stream()
.map(Dish::getName)//map映射
.collect(toList());
菜单流Stream<Dish>
经过map映射操作Dish::getName
转换成了字符串流Stream<String>
,即菜名流。
流的扁平化操作flatMap,通俗来将就是将多个流合并为一个流。
假如我们需要将给定的单词列表[“Hello”,”World”]拆分为去重的字母列表[“H”,”e”,”l”, “o”,”W”,”r”,”d”],该怎么做?
尝试1:使用map与distinct
//取得单词流Stream<String> 每个单词为一个字符串
words.stream()
//映射成Stream<String[]>,每个单词转换成了组成该单词的字符数组
.map(word -> word.split(""))
//去重,依然是String[]
.distinct()
//结果是List<String[]>
.collect(toList());
尝试1宣告失败!
尝试2:使用map和Arrays.stream()
//取得单词流Stream<String> 每个单词为一个字符串
words.stream()
//映射成Stream<String[]>,每个单词转换成了组成该单词的字符数组
.map(word -> word.split(""))
//Arrays::stream将每个字符数组转成字符流Stream<String>
.map(Arrays::stream)
.distinct()
//结果是List<Stream<String>>
.collect(toList());
尝试2宣告失败!
尝试3:使用flatMap
List<String> uniqueCharacters =
//取得单词流Stream<String> 每个单词为一个字符串
words.stream()
//映射成Stream<String[]>,每个单词转换成了组成该单词的字符数组
.map(w -> w.split(""))
//flatMap将每个字符数组转成的字符流Stream<String>扁平化为单个流Stream<String>
.flatMap(Arrays::stream)
//去除重复字符
.distinct()
//获取去重后的字符列表List<String>
.collect(Collectors.toList());
尝试3宣告成功!
查找菜单中是否存在素食,一旦匹配到素食即退出并返回true
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("找到素食!");
}
检测菜单中所有菜是不是都是健康食品(假设热量小于1000就是健康的):
if(menu.stream().allMatch(d -> d.getCalories() < 1000)){
System.out.println("所有菜都是健康的,因为热量都小于1000");
}
if(menu.stream().noneMatch(d -> d.getCalories() >= 1000)){
System.out.println("所有菜都是健康的,因为热量都不大于1000");
}
例如找到菜单中的任何一样素食:
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
findAny返回当前流中的任意一个元素。
java.util.Optional<T>
是一个容器类,它包裹着T,如上面代码中的Optional<Dish>
可以理解装着Dish的一个容器,且该Dish允许为空,可以通过optional.get()方法获取该容器装的东西,该系列后面的文章会详细介绍Optional类的用法。
找到第一个平方能被3整除的数:
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
相比于findAny,findFirst在并行上有更多的限制,如果不关心找到哪个元素,推荐用findAny。
归约操作是指将流中的所有元素反复组合起来,得到一个值。比如,将流中的元素求和:int sum = numbers.stream().reduce(0, (a, b) -> a + b);
为什么说是反复组合起来?
求和归约操作中是将指定的初始值0(reduce方法的第一个参数)与流中的第一个元素相加(表达为reduce的第二个参数BinaryOperator,我们传递是Lambda表达式(a, b) -> a + b)得到一个中间值后与流中的第二个元素相加,直至流中的所有元素都被累加完,得到最终的求和结果。
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
区别是返回值变成了Optional。
//求最大值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
//求最小值
Optional<Integer> min = numbers.stream().reduce(Integer::min);
注:reduce操作最重要的就是需要指定一个Lambda表达式或方法引用来将流中的两个元素依次结果来产生一个新值。