Java中的函数式编程(六)流Stream基础
写在前面
如果说函数式接口和lambda表达式是Java中函数式编程的基石,那么stream就是在基石上的最富丽堂皇的大厦。
只有熟悉了stream,你才能说熟悉了Java 的函数式编程。
本文主要介绍Stream的基础概念和基本操作,让大家对Stream有一个初步的理解。
本文的示例代码可从gitee上获取:https://gitee.com/cnmemset/javafp
stream的概念
首先,看一个典型的stream例子:
public static void simpleStream() {
List<String> words = Arrays.asList("hello", "world", "I", "love", "you");
int letterCount = words.stream()
.filter(s -> s.length() > 3) // 过滤掉长度小于等于3的单词
.mapToInt(String::length) // 将每个单词映射为单词长度
.sum(); // 计算总长度 5(hello) + 5(world) + 4(love) = 14
// 输出为 14
System.out.println(letterCount);
}
在上述例子中,我们将字符串列表 words 作为stream的数据源,然后执行了 filter-map-reduce 的系列操作(sum方法属于 reduce 操作),后面会详细介绍map和reduce 操作。如果你有大数据的编程经验,会更容易理解map和reduce的含义。
stream的定义比较晦涩,大致可以理解为是一个支持串行或并行操作的数据元素序列。它具备以下几个特点:
- 首先,stream不是一种数据结构,它并不存储数据。stream是某个数据源之上的数据视图。数据源可以是一个数组,或者是一个Collection类,甚至还可以是I/O channel。它通过一个计算管道(a pipeline of computational operations),对数据源的数据进行filter-map-reduce的操作。
- 其次,stream天生支持函数式编程。函数式编程的一个重要特点就是不会修改变量的值(没有“副作用”)。而对stream的任何操作,都不会修改数据源中的数据。例如,对一个数据源为Collection的stream进行filter操作,只会生成一个新的stream对象,而不会真的删除底层数据源中的元素。
- 第三,stream的许多操作都是惰性求值的(laziness-seeking)。惰性求值是指该操作只是对stream的一个描述,并不会马上执行。这类惰性的操作在stream中被称为中间操作(intermediate operations)。
- 第四,stream呈现的数据可以是无限的。例如Stream.generate可以生成一个无限的流。我们可以通过 limit(n) 方法来将一个无限流转换为有限流,或者通过 findFirst() 方法终止一个无限流。
- 最后,stream中的元素只能被消费1次。和迭代器 Iterator 相似,当需要重复访问某个元素时,需要重新生成一个新的stream。
stream的操作可以分成两类,中间操作(intermediate operations)和终止操作(terminal operations)。一个stream管道(stream pipeline)是由一个数据源 + 0个或多个中间操作 + 1个终止操作组成的。
中间操作:
中间操作(intermediate operations)指的是将一个stream转换为另一个stream的操作,譬如filter和map操作。中间操作都是惰性的,它们的作用仅仅是描述了一个新的stream,不会马上被执行。
终止操作:
终止操作(terminal operations)则指的是那些会产生一个新值或副作用(side-effect)的操作,譬如count 和 forEach 操作。只有遇到终止操作时,之前定义的中间操作才会真正被执行。需要注意,当一个stream执行了一个终止操作后,它的状态会变成“已消费”,不能再被使用。
为了证实“中间操作都是惰性的”,我们设计了一个实验性的示例代码:
public static void intermediateOperations() {
List<String> words = Arrays.asList("hello", "world", "I", "love", "you");
System.out.println("start: " + System.currentTimeMillis());
Stream<String> interStream = words.stream()
.filter(s -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// do nothing
}
return s.length() > 3;
});
IntStream intStream = interStream.mapToInt(s -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// do nothing
}
return s.length();
});
// 因为 filter 和 map 操作都属于中间操作,并不会真正执行,
// 所以它们不受 Thread.sleep 的影响,耗时很短
System.out.println("after filter && map: " + System.currentTimeMillis());
int letterCount = intStream.sum();
// sum 属于终止操作,会执行之前定义的中间操作,
// Thread.sleep 被真正执行了,耗时为 5(filter) + 3(mapToInt) = 8秒
System.out.println("after sum: " + System.currentTimeMillis());
// 输出为 14
System.out.println(letterCount);
}
上述代码的输出类似:
start: 1633438922526
after filter && map: 1633438922588
after sum: 1633438930620
14
可以看到,上述代码验证了“中间操作都是惰性的”:打印“start”和打印“after filter && map”之间只隔了几十毫秒,而打印“after sum”则在8秒之后,证明了只有在遇到 sum 操作后,filter 和 map 中定义的函数才真正被执行。
生成一个stream对象
Java 8中,引入了4个stream的接口:Stream、IntStream、LongStream、DoubleStream,分别对应Object类型,以及基础类型int、long和double。如下图所示:
在Java中,与stream相关的操作基本都是通过上述的4个接口来实现的,不会涉及到具体的stream实现类。要得到一个stream,通常不会手动创建,而是调用对应的工具方法。
常用的工具方法包括:
- Collection方法:Collection.stream() 或 Collection.parallelStream()
- 数组方法:Arrays.stream(Object[])
- 工厂方法:Stream.of(Object[]), IntStream.range(int, int) 或 Stream.iterate(Object, UnaryOperator) 等等
- 读取文件方法:BufferedReader.lines()
- 类 java.nio.file.Files 中,也提供了Stream相关的API,例如 Files.list, Files.walk 等等
Stream的基本操作
我们以接口Stream为例,先介绍stream的一些基本操作。
forEach()
Stream中的forEach方法和Collection中的forEach方法相似,都是对每个元素执行指定的操作。
forEach方法签名为:
void forEach(Consumer<? super T> action)
forEach方法是一个终止操作,意味着在它之前的所有中间操作都将会被执行,然后再马上执行 action 。
filter()
filter方法的方法签名是:
Stream<T> filter(Predicate<? super T> predicate)
filter方法是一个中间操作,它的作用是根据参数 predicate 过滤元素,返回一个只包含满足predicate条件元素的Stream。
示例代码:
public static void filterStream() {
List<String> words = Arrays.asList("hello", "world", "I", "love", "you");
words.stream()
.filter(s -> s.length() > 3) // 过滤掉长度小于等于3的单词
.forEach(s -> System.out.println(s));
}
上述代码输出为:
hello
world
love
limit()
limit方法签名为:
Stream<T> limit(long maxSize);
limit方法是一个短路型(short-circuiting)的中间操作,作用是将当前的Stream截断,只留下最多 maxSize 个元素组成一个新的Stream。短路型(short-circuiting)的含义是指将一个无限元素的Stream转换为一个有限元素的Stream。
例如,Random.ints 可以生成一个近似无限的随机整数流,我们可以通过limit方法限制生成随机整数的个数。示例代码:
public static void limitStream() {
Random random = new Random();
// 打印左闭右开区间中 [1, 100) 中的 5 个随机整数
random.ints(1, 100)
.limit(5)
.forEach(System.out::println);
}
上述代码的输出类似:
90
31
31
52
63
distinct()
distinct的方法签名是:
Stream<T> distinct();
distinct是一个中间操作,作用是返回一个去除重复元素后的Stream。
作者曾遇到过一个有趣的场景:要生成10个不重复的随机数字。可以结合Random.ints (Random.ints 可以生成一个近似无限的随机整数流)方法来实现这个需求。示例代码如下:
public static void distinctStream() {
Random random = new Random();
// 在左闭右开区间中 [1, 100) 随机生成 10 个不重复的数字
random.ints(1, 100)
.distinct()
.limit(10)
.forEach(System.out::println);
/*
// 一个有趣的问题,如果 limit 方法放在 distinct 前面,
// 结果和上面的代码有什么区别吗?
// 欢迎加群讨论。
random.ints(1, 100)
.limit(10)
.distinct()
.forEach(System.out::println);
*/
}
sorted()
sorted的方法签名有两个,分别是:
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
前者是按照自然顺序排序,后者是根据指定的比较器进行排序。
sorted方法是一个中间操作,和Collection.sort方法作用相似。
示例代码如下:
public static void sortedStream() {
List<String> list = Arrays.asList("Guangdong", "Fujian", "Hunan", "Guangxi");
// 自然排序
list.stream().sorted().forEach(System.out::println);
System.out.println("===============");
// 对省份进行排序,首先按照长度排序,如果长度一样,则按照字母顺序排序
list.stream().sorted((first, second) -> {
int lenDiff = first.length() - second.length();
return lenDiff == 0 ? first.compareTo(second) : lenDiff;
}).forEach(System.out::println);
}
上述代码的输出为:
Fujian
Guangdong
Guangxi
Hunan
===============
Hunan
Fujian
Guangxi
Guangdong
结语
欢迎来到 Java 的函数式编程世界!!!
本文介绍了 Stream 的概念和基本操作。大家尤其要理解中间操作和终止操作的概念。
认真阅读完本文后,你应该对 Stream 有了一个初步的认识,但这只是 Stream 编程的入门,更有趣更有挑战性更有可玩性的还是随后即将要介绍的 map-reduce 操作。
Java中的函数式编程(六)流Stream基础的更多相关文章
- Java 中的函数式编程(Functional Programming):Lambda 初识
Java 8 发布带来的一个主要特性就是对函数式编程的支持. 而 Lambda 表达式就是一个新的并且很重要的一个概念. 它提供了一个简单并且很简洁的编码方式. 首先从几个简单的 Lambda 表达式 ...
- Java中的函数式编程(八)流Stream并行编程
写在前面 在本系列文章的第一篇,我们提到了函数式编程的优点之一是"易于并发编程". Java作为一个多线程的语言,它通过 Stream 来提供了并发编程的便利性. 题外话: 严格来 ...
- Java中的函数式编程(七)流Stream的Map-Reduce操作
写在前面 Stream 的 Map-Reduce 操作是Java 函数式编程的精华所在,同时也是最为复杂的部分.但一旦你啃下了这块硬骨头,那你就真正熟悉Java的函数式编程了. 如果你有大数据的编程经 ...
- Java中的函数式编程(二)函数式接口Functional Interface
写在前面 前面说过,判断一门语言是否支持函数式编程,一个重要的判断标准就是:它是否将函数看做是"第一等公民(first-class citizens)".函数是"第一等公 ...
- Java中的函数式编程(五)Java集合框架中的高阶函数
写在前面 随着Java 8引入了函数式接口和lambda表达式,Java 8中的集合框架(Java Collections Framework, JCF)也增加相应的接口以适应函数式编程. 本文的 ...
- Java中的函数式编程(三)lambda表达式
写在前面 lambda表达式是一个匿名函数.在Java 8中,它和函数式接口一起,共同构建了函数式编程的框架. lambda表达式乍看像是匿名内部类的一种语法糖,但实际上,它们是两种本质不同的事物 ...
- Java中的函数式编程(四)方法引用method reference
写在前面 我们已经知道,lambda表达式是一个匿名函数,可以用lambda表达式来实现一个函数式接口. 很自然的,我们会想到类的方法也是函数,本质上和lambda表达式是一样的,那是否也可以用类 ...
- 随便聊聊 Java 8 的函数式编程
函数式编程(Functional Programming) 首先,我们来了解一个叫做"编程范式"的概念. 什么是"编程范式"呢?简单来说就是指导我们编程的方法论 ...
- 读懂Java中的Socket编程
Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的S ...
随机推荐
- Django——实现评论功能(包括评论回复)
提示:(1)功能不全面,仅仅实现评论(2)样式简单 1.项目目录结构 2.模型 from django.db import models from django.contrib.auth.models ...
- Linux基础命令(基于CentOS7)
1.帮助相关命令 man 查看普通命令的帮助 --help 只能查看内置命令 info 查看一个命令的更多信息 type 查看是否为内置命令 2.关机重启 shutdown -h 关机 -r 重启 - ...
- 浅析Is-a,Has-a与like-a
在面向对象的设计领域里,有很多设计思路,主要有三种:is-a.has-a.like-a. 这三种在java的类.接口.抽象类中很多体现,下面简述一下其定义. 1.Is-a(继承关系) is-a,顾名思 ...
- python 修改图像大小和分辨率
1 概念: 分辨率,指的是图像或者显示屏在长和宽上各拥有的像素个数.比如一张照片分辨率为1920x1080,意思是这张照片是由横向1920个像素点和纵向1080个像素点构成,一共包含了1920x108 ...
- Gitlab(1)- 简单介绍
什么是 Gitlab 一个开源分布式版本控制系统 开发语言:Ruby 功能:管理项目源代码.版本控制.代码复用与查找.权限管控 Git 家族成员 Git:是一种版本控制系统,是一个命令,是一种工具 G ...
- 植入式Web前端开发
在博客园.凡科建站和其他的一些CMS系统中,提供有允许管理者向网页中插入自定义HTML代码的功能,我将其称之为"植入式"的Web前端代码. 因为CSS和JavaScript可以直接 ...
- 【Nginx】Linux常用命令------启动、停止、重启
启动 启动代码格式:nginx安装目录地址 -c nginx配置文件地址 例如: [root@LinuxServer sbin]# /usr/local/nginx/sbin/nginx -c /us ...
- vue开发流程
在安装node.js 测试安装 在cmd 下输入node 如查能正确输出命令提示符,表明安装好node 测试安装npm -v 如果能成功出现版本信息表示安装好npm 安装配置 G码云 或 ...
- 5-21python数据类型
一.字符串,是不可变数据类型,所有字符串的方法都不会修改字符串的值,使用字符串的方法后都是生成了一个新的字符串.就因为字符串是不可变变量! 字符串的方法 1. strip(),默认去空格,但是当()中 ...
- HDU1548 Building Roads
A strange lift Description There is a strange lift.The lift can stop can at every floor as you want, ...