博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用流
阅读量:6622 次
发布时间:2019-06-25

本文共 7830 字,大约阅读时间需要 26 分钟。

筛选和切片

filter

filter 会接受一个谓词作为参数,并返回符合该条件的元素流。

List
vegetarianMenu = menu .stream() .filter(Dish::getVegetarian) .collect(Collectors.toList());

会筛选出素食的菜肴。

distinct

distinct 会返回一个元素各异的流,也就是去重。

List
caloriesMenu = menu .stream() .filter((Dish dish) -> 350 == dish.getCalories()) .map(Dish::getCalories) .distinct() .collect(Collectors.toList());

会筛选出卡路里为350的菜肴并去重(米饭和对虾二选一)。

limit(n)

limit(n) 会返回一个不超过给定长度的流。

List
caloriesMenu = menu .stream() .filter((Dish dish) -> 350 < dish.getCalories()) .limit(3) .collect(Collectors.toList());

List 是有序集合所以会顺序筛选出三个,而如果是 Set 无序的则不会以任何顺序排列。

skip(n)

skip(n) 返回一个人掉了前 n 个元素的流,和 limit(n) 是互补的。

List
caloriesMenu = menu .stream() .filter((Dish dish) -> 350 < dish.getCalories()) .skip(3) .collect(Collectors.toList());

映射

map 返回一个映射好的元素流。如果我们要找出每个菜肴的名称有多长,那么可以这样思考:第一,我们需要知道菜肴的名称。第二,计算菜肴名称的长度。

List
caloriesMenu = menu .stream() // 映射名称字符串 .map(Dish::getName) // 映射字符串长度 .map(String::length) .collect(Collectors.toList());

流的扁平化

返回菜肴名称的长度已经做到了,如果有奇怪的需求,比如将菜肴名称打碎成字符并去重……

List
caloriesMenu = menu .stream() // 映射名称字符串 .map(Dish::getName) // 将字符串分割为字符数组 String[] .map((String string) -> string.split("")) // Arrays.stream() 可以接收一个数组并产生一个流,再调用 flatMap 将其转成单个流 .flatMap(Arrays::stream) // 去重 .distinct() // 保存 .collect(Collectors.toList());

扁平化在这里就是让字符数组不是分别映射成一个流,而是映射成流的内容,然后将使用 Arrays::stream 时生成的单个流都被合并起来。

查找和匹配

anyMatch

anyMatch 可以检查谓词是否至少匹配一个元素。

// 检查是否至少含有一道是素食的菜肴        Boolean b = menu                .stream()                .anyMatch(Dish::getVegetarian);

allMatch

allMatch 可以检查谓词是否全部匹配。

// 检查是否全部是素食        Boolean b = menu                .stream()                .anyMatch(Dish::getVegetarian);

noneMatch

noneMatch 可以检查谓词是否全部不匹配。

// 检查是否全部不是素食        Boolean b = menu                .stream()                .noneMatch(Dish::getVegetarian);

以上这三种操作都用到了所谓的短路,也和 Java 中!、&&和||运算符意义一样。

findAny

findAny 将返回当前流中的任意元素。

Optional
dishOptional = menu .stream() .findAny();

Optional<T> 是一个容器类,代表一个值存在或不存在。有可能 findAny 什么也没找到,所以如果用 Dish 对象直接装可能会报空指针错误。本章不会深入了解 Optional,但可以先简单介绍一下这个类的一些有用的方法。

  • isPresent() 将在非空的时候返回 true。

  • ifPresent(Consumer<? super T> consumer) 将在非空的时候执行 Consumer 的代码,再第二章的时候我们知道 Consumer 可以接受一个对象并进行操作。

  • T get() 将在非空的时候返回值,否则会抛出没有这个元素的异常。

  • T orElse(T other) 将在非空的时候返回值,否则返回一个默认值。

比如上面的代码可以变成这样

menu.stream().findAny().ifPresent(System.out::println);

就可以打印输出任意菜肴的信息。

findFirst

findAny 类似,只不过返回有序集合的第一个元素。

Optional
dishOptional = menu .stream() .findFirst();

那么 findAnyfindFirst 有什么区别呢?在并行计算里面,找到第一个元素会需要很多思考,所以一般在并行处理时用 findAny

归纳

reduce 操作可以吧一个流中的元素组合起来,比如菜单里的总卡路里。此类查询需要将流中所有元素反复结合起来,得到一个值。这样的查询操作可以被归类为 归约 操作,用函数式编程语言的术语来说,这称为 折叠

元素求和

平常使用 for-each 来处理总卡路里我们一般会这样做

Integer integer = 0;        for (Dish dish : menu) {            integer += dish.getCalories();        }

在流中可以使用 reduce 方法来处理

Integer integer = menu                .stream()                .map(Dish::getCalories)                .reduce(0, (Integer a, Integer b) -> a + b);

好像有点复杂,没关系,接下来可以详细讲解。

首先我们用 map 将 Dish 对象映射成其卡路里值的 Integer 类型。然后调用 reduce 将每个映射好的 Integer 组合(相加)。其中 reduce 方法第一个参数代表初始值,后面跟着一个 BinaryOperator<T> 的函数式接口。这个函数式接口目的就是将两个元素组合。

可以看一下 BinaryOperator 的源码

@FunctionalInterfacepublic interface BinaryOperator
extends BiFunction
{ public static
BinaryOperator
minBy(Comparator
comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; } public static
BinaryOperator
maxBy(Comparator
comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; }}

实际上我们调用的是第三章有提到过的 BiFunction 里的 apply 方法

@FunctionalInterfacepublic interface BiFunction
{ R apply(T t, U u);}

BinaryOperator 它接受两个同类型参数并返回一个同类型的对象。

所以我们可以这样来写

BinaryOperator
integerBinaryOperator = (Integer a, Integer b) -> a + b;

代表计算 a + b。而 reduce 自带循环,所以会在背后进行类似于 a += b 一样的逻辑操作。reduce还有一个重载的版本,可以不接受初始值返回一个 Optional 对象。

Optional
integer = menu .stream() .map(Dish::getCalories) .reduce((Integer a, Integer b) -> a + b);

用 Optional 的意义与之前一样,考虑到了集合为空的情况。

最大和最小

reduce 同样可以用来做大小比较,因为其需要的 BinaryOperator 自带有比较方法。

最大

Optional
integer = menu .stream() .map(Dish::getCalories) .reduce(Integer::max);

其中 reduce(Integer::max) 是

reduce((Integer integer1, Integer integer2) -> Integer.max(integer1, integer2))

的缩写。计算最小值只需要将 max 换成 min 就行。归纳方法的对比传统的 for-each 优势是可以把内部迭代抽象化,这让其内部可以并行化而不用我们自己去实现并行处理。

但是这种并行是有限制的,不是将所有的 stream 换成 parallelStream 就行了。

像 map 或 filter 等操作会从输入流中获取每一个元素并在输出流中得到至多一个结果。这些操作一般是无状态的,做并行是可行的。

像 reduce、max 等操作需要内部状态来累积结果,因为求和肯定是需要之前数的和加上现在选择的这个元素,但无论哪个都是有限的,所以称为有界,是可以直接做并行。

相反,如同 distinct 操作,是需要接受一个流再生成一个流,并且去重操作是需要知道先前的历史流是什么样的,例如把所有质数倒序,就需要最大的那个质数,但这是不存在的,所以称为无界状态。做并行需要好生思考一番。

关于去重,其实可以换一种思路,将有序集合(List)转为无序集合(Set)就自动去重了。

Set
caloriesMenu = menu .stream() .filter((Dish dish) -> 350 == dish.getCalories()) .map(Dish::getCalories) .collect(Collectors.toSet());

数值范围

在和数字打交道时,比较常用的就是在一个数值范围内生成数字。Java 8引入了两个可以用于 IntStream 和 LongStream 的静态方法,帮助生成范围:range 和 rangeClosed。是不包含结束值的,比如传入(0,100)就会运算到99结束。

比如我们需要统计0~100里偶数的个数(包含100)

long l = IntStream                .rangeClosed(0, 100)                .filter((int n) -> n % 2 == 0)                .count();

这样就统计出了51个偶数个数,如果不包含100就用 range,会统计50个。

构建流

现在我们已经能够使用 Stream 从集合生成流了。那么如何从值序列、数组和文件生成流,甚至函数来创建无限流?

创建一个空流

Stream
stringStream = Stream.empty();

由值创建流

Stream
stringStream = Stream.of("qwe", "asd", "zxc"); stringStream.map(String::toUpperCase).forEach(System.out::println);

这样会把小写字符串全部转成大写。

由数组创建流

int[] ints = {1, 2, 3, 4, 5};        int sum = Arrays.stream(ints).sum();

这样会把所有的数字相加。

由文件生成流

/*        /Users/cciradih/java:                Java Platform, Standard Edition (Java SE) lets you develop and deploy Java applications on desktops and servers,        as well as in today's demanding embedded environments. Java offers the rich user interface, performance,        versatility, portability, and security that today's applications require.         */        // 不重复的单词数        long uniqueWords = 0;        // 预处理获取流,使用后不用手动关闭流。        try (Stream
stringStream = Files.lines(Paths.get("/Users/cciradih/java"), Charset.defaultCharset())) { // 从流中统计 uniqueWords = stringStream // 扁平化字符串数组 .flatMap((String line) -> Arrays.stream(line.split(" "))) // 去重 .distinct() // 统计 .count(); } catch (IOException e) { e.printStackTrace(); }

这里是用到了新的 NIO,以便利用 Stream。我们使用 Files.lines 得到流,其中每个元素就是文本里的一行。然后使用 split 拆分成单词,并用 flatMap 将其扁平化。最后用 distinct 去重再用 count 统计。

由函数生成流

Stream 提供了两个静态方法来从函数生成流 iterate 和 gengrate。这两个操作都可以创建无线流。

iterate

Stream.iterate(0, (Integer integer) -> integer + 2);

这会创建一个从0开始无限的偶数流。

gengrate

Stream.generate(Math::random);

这会创建一个0到1之间无限的随机数。

这一章详细讲了流的使用场景和需要注意的地方,特别是和传统的操作做对比,能更好地支持并行处理。

Java 8 实战 第五章 使用流 读书笔记

欢迎加入咖啡馆的春天(338147322)。

转载地址:http://mljpo.baihongyu.com/

你可能感兴趣的文章
Servlet
查看>>
Mapper文件的sql语句
查看>>
ROS系统MoveIt玩转双臂机器人系列(一)--ROS机器人建模
查看>>
TypeScript和Node模块解析策略
查看>>
【markdown】markdown常用语法
查看>>
mysql 数据库账户设置
查看>>
背包系列 hdu 3535 分组背包
查看>>
Django orm moldes filter常用查询条件
查看>>
python3利用pyftpdlib模块启动ftp服务
查看>>
PHP 源码扩展开发相关记录
查看>>
linux 编译安装pureFTP
查看>>
分割字符串
查看>>
Tomcat9.0环境搭建与源码编译
查看>>
javascript ES6 Proxy
查看>>
机器学习前沿热点–Deep Learning
查看>>
Sunglasses Replicas Oakley
查看>>
用vlc搭建简单流媒体服务器(UDP和TCP方式)
查看>>
c语言机构体,枚举,宏
查看>>
今天正式开通了博客园,好激动啊!
查看>>
CSS3 实现别样图型
查看>>