赞
踩
本节会详解介绍如何使用流,以发挥出它的强大功能,主要包括:
1)筛选,切片,映射
2)查找,匹配和归约
3)使用数值范围等数值流
4)从多个源创建流
5)无限流
Stream接口支持filter方法,该方法接收Predicate谓词,而Predicate之前已经介绍了是函数式接口,所以我们可以很方便的传递lambda,对流元素做筛选。
类似sql中的distinct关键字,Stream接口也提供了distinct方法,用于将重复元素过滤出去,如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 4, 5);
numbers.stream().distinct().forEach(System.out::println);
// result: 1,2,3,4,5
通过limit(n)方法,会返回一个不超过给定长度的流。示例:
List<Dish> dishes = menu.stream().filter(e -> e.getCalories() > 300).limit(3).collect(toList());
// 只筛选出符合谓词的前三个元素就立即返回结果,实现了截断效果
用的比较少,如可以跳过指定个数的元素:skip(n)。 如果流中元素不足n个,则返回一个空流。示例:
List<Dish> dishes = menu.stream().filter(e -> e.getCalories() > 300).skip(1).collect(toList());
这是一个非常常用的功能,如取对象某个属性再做处理,就像sql中取某一列。
Stream支持map()方法,接收一个函数(Function),这个函数会被应用到每个元素上,并将其映射为新的元素。注意调用map()方法后会改变流的类型,变成映射后新元素的类型。如:
List<String> dishes = menu.stream().filter(e -> e.getCalories() > 300).map(Dish::getName).collect(toList());
有时候需要实现这样的功能:
对于一张单 词表,如何返回一张列表,列出里面各不相同的字符呢?例如,给定单词列表 [“Hello”,“World”],你想要返回列表[“H”,“e”,“l”, “o”,“W”,“r”,“d”]。
只用map无法解决,你可能想这样实现:
words.stream().map(w -> w.split("")).distinct().collect(toList());
但实际达不到需要的效果,因为调用map后生成的流类型是Stream<String[]>, 而我们期望的是Stream<String>。这时需要用到flatMap来解决。
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream) // 流的扁平化:将多个流合并为一个流
.distinct()
.collect(Collectors.toList());
一个常见的数据处理操作是看集合中是否有满足某个条件的数据。Stream API通过allMatch, anyMatch, noneMatch, findFirst, finaAny方法提供了类似功能。
public class MatchSample { public static void main(String[] args) { List<Dish> menus = new ArrayList<>(List.of( new Dish("大虾", 100), new Dish("大鱼", 200), new Dish("大兔", 300), new Dish("大狗", 400), new Dish("大猪", 500) )); boolean match = menus.stream().anyMatch(e -> e.getCalories() > 300); if (match) { System.out.println("存在卡路里大于300的食物"); } } }
public class AllMatchSample { public static void main(String[] args) { List<Dish> menus = new ArrayList<>(List.of( new Dish("大虾", 100), new Dish("大鱼", 200), new Dish("大兔", 300), new Dish("大狗", 400), new Dish("大猪", 500) )); boolean match = menus.stream().allMatch(e -> e.getCalories() > 300); if (match) { System.out.println("所有食物卡路里大于300"); } else { System.out.println("不是所有食物卡路里大于300"); } } }
public class NoneMatchSample { public static void main(String[] args) { List<Dish> menus = new ArrayList<>(List.of( new Dish("大虾", 100), new Dish("大鱼", 200), new Dish("大兔", 300), new Dish("大狗", 400), new Dish("大猪", 500) )); boolean match = menus.stream().noneMatch(e -> e.getCalories() > 300); if (match) { System.out.println("所有食物卡路里都不大于300"); } else { System.out.println("有食物卡路里大于300"); } } }
java.util.Optional<T>是一个容器类,java8新加入,可代表一个值存在or不存在,引入的目的是为了避免直接返回null。
Optional中几个常见的方法:
1)isPresent(),将在包含值时返回true,反之返回false
2) ifPresent(Consumer<T> c) 会在值存在时执行指定的消费逻辑
3)T get() 会在值存在时返回值,否则返回NoSuchElement异常
4)T orElse(T other)会在值存在时返回值本身,否则返回一个默认值other。
如需要实现如下功能:
// 打印任意一个卡路里大于400的菜肴
menu.stream()
.filter(e -> e.getCalaris() > 400)
.findAny()
.ifPresent(e -> System.out.println(e.getName()));
findFirst: 查走第一个元素
findAny: 查找任意一个元素
// 打印任意一个卡路里大于400的菜肴
menu.stream()
.filter(e -> e.getCalaris() > 400)
.findAny()
.ifPresent(e -> System.out.println(e.getName()));
// 打印第一个卡路里大于400的菜肴
menu.stream()
.filter(e -> e.getCalaris() > 400)
.findFirst()
.ifPresent(e -> System.out.println(e.getName()));
findFirst vs findAny
当明确需要取首元素时使用findFirst,否则用findAny即可。因为它在使用并行流时限制较少。
目前用过的流终端操作有:
1)返回boolean型:anyMatch, allMatch, NoneMatch
2) 返回void: forEach
3)返回Integer: count
4) 返回Optional: findFirst, findAny
5)返回集合:collect(toList)
下面介绍一种新的终端操作:reduce。即可以使用归约操作将流中的元素组合起来,实现强大的功能。如计算菜肴的总卡路里,或挑选出卡路里最高的菜。
示例如下:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
reduce接收两个参数:
1)一个初始值,这里是0
2)一个BinaryOperator<T>将两个元素结合起来产生一个新值。
整体过程如下:
1)首先0作为第一个参数a的值,从流中取出值作为b的值,执行求和得到累计值。
2)将累计值作为a的值,再去流中下一个元素作为b的值,再执行求和得到累计值。
3)依次迭代直到流中最后一个元素,终止,得到最后的归约结果。
// 求最大值 public class MaxReduceSample { public static void main(String[] args) { List<Dish> menus = new ArrayList<>(List.of( new Dish("大虾", 100), new Dish("大鱼", 200), new Dish("大兔", 300), new Dish("大狗", 400), new Dish("大猪", 500) )); //Integer maxCalories = menus.stream().map(Dish::getCalories).reduce(0, (i, j) -> i > j ? i : j); Integer maxCalories = menus.stream().map(Dish::getCalories).reduce(0, Integer::max); System.out.println(maxCalories); } }
求最小值不再举例,写法几乎一致
map-reduce通常一起用,称为map-reduce模式, 因Google用它来进行网络搜索而出名,因为它很容易并行化
还是先看下求和的例子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
此种写法能计算出结果,但隐含了自动拆箱装箱,浪费了性能。为此引入了三个针对原始类型的流接口来解决这个问题,分别是:IntStream, DoubleStream和LongStream,分别将流中的元素转化为int, double和long,从而避免了暗含的自动装箱成本。
常见操作如下:
将流转化为特定版本数值流的方法如下:mapToInt, mapToDouble和mapToLong。转化为数值流后可使用一些快捷方法完成计算:
// 更快捷的计算总卡路里
int sum = menu.stream().mapToInt(Dish::getCalories).sum();
// 还支持如max, min, average等常见方法
数值流类型是IntStream, DoubleStream和LongStream, 某些场景下又想转化回对象流Stream<T>,可执行如下操作:
IntStream i = menu.stream().mapToInt(Dish::getCalories);
// return to Stream<T>
Stream<Integer> s = i.boxed();
对于数值流,也有三个Optional特殊版本,分别是OptionalInt, OptionalDouble和OptionalLong。
例如:要找到IntStream中的最大值,可以调用max方法,会返回一个OptionalInt:
OptionalInt i = menu.stream().mapToInt(Dish::getCalories).max();
int max = i.orElse(1);
IntStream.range(start, end): 可用来生成数值范围序列。
IntStream.rangeClosed(start, end): 可用来生成数值范围序列。
两者的区别是:range不包含end, rangeClosed包含end。
示例:
long total = IntStream.range(0, 100).filter(e -> e % 2 == 0).count();
System.out.println(total);
// result: 50
long total = IntStream.rangeClosed(0, 100).filter(e -> e % 2 == 0).count();
System.out.println(total);
// result: 51
示例:
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); stream.map(String::toUpperCase).forEach(System.out::println);
可初始化一个空流
Stream<String> emptyStream = Stream.empty();
Arrays.stream(new String[]{"a", "b", "c"})
java.nio.file.Files有很多静态方法支持返回一个流。如下面的示例用于返回一个文本文件中不同单词的个数。
/** * 演示从文本文件获取流,做相应处理 */ public class FileStreamSample { public static void main(String[] args) { // Stream接口天生实现了AutoCloseable接口,所有可以使用try...with...resource语法 try(Stream<String> lines = Files.lines(Paths.get("/Users/liupeijun/git/learn/1.txt"))) { // 使用流的flatMap方法扁平化流 long count = lines.flatMap(e -> Arrays.stream(e.split(" "))).distinct().count(); System.out.println(count); } catch (IOException e) { e.printStackTrace(); } } }
StreamAPI提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generte来创建无限流。它们都是按需创建值,如果不加限制可以一直计算下去,所有一般都要加上 限制limit(n),以避免打印无穷多个值。
iterate
示例如下:
// 生成一个从0开始的偶数序列流,限制最大生成100个序列
Stream.iterate(0, n -> n + 2).limit(100).forEach(System.out::println);
// result: 0, 2, 4, 6, ..., 198
iterate第一参数是初始值,第二个参数是UnaryOperator<T>类型。
一个实用示例:生成斐波那契数列
格式是:[[0, 1], [1, 2], [2, 3], [3, 5] …]
public class FibonacciSample { public static void main(String[] args) { Stream.iterate(new long[]{0, 1}, t -> new long[]{t[1], t[0] + t[1]}) .limit(80) .forEach(e -> System.out.println("[" + e[0] + "," + e[1] + "]")); } } //result: [0,1] [1,1] [1,2] [2,3] [3,5] [5,8] [8,13] [13,21] [21,34] ...
generate
也可以按需生成一个无限流,但不是依次对每个新生成的值应用函数,它接收一个Supplier<T>提供新值,如:
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
// result:
0.9410810294106129
0.6586270755634592
0.9592859117266873
0.13743396659487006
0.3942776037651241
具体generate的进一步应用,这里不再展开。
本节系统学习了如何使用流,能更高效的处理集合了。总结一下知识点:
1)可以使用filter, distinct, skip,limit对流做筛选和切片
2)可以使用map和flatMap做映射和流的扁平化
3)可以使用findFirst和findAny查找流中元素
4)可以使用allMatch, anyMatch, noneMatch查询是否匹配
5)可以利用map-reduce模式实现流元素的归约,如求和,最大,最小等
6)区分有状态操作(reduce, sorted, distinct)和无状态操作(map, filter)
7) 从数组,集合,文件创建流;无限流(iterate,generate)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。