Java 流式操作
流式操作,是 Java 8
除了Lambda
表达式外的又一重大改变。学习流式操作,就是学习java.util.stream
包下的API
,我们称之为Stream API
,它把真正的函数式编程引入到了 Java 中。
本小节我们将了解到什么是Stream
,为什么使用Stream API
, 流式操作的执行流程,如何实例化Stream
,Stream
的中间操作、Stream
的终止操作等内容。
1. 什么是 Stream
Stream
是数据渠道,用于操作数据源所生成的元素序列,它可以实现对集合(Collection
)的复杂操作,例如查找、替换、过滤和映射数据等操作。
我们这里说的Stream
不同于java
的输入输出流。另外,Collection 是一种静态的数据结构,存储在内存中,而Stream
是用于计算的,通过CPU
实现计算。注意不要混淆。
Tips:
Stream
自己不会存储数据;Stream
不会改变源对象,而是返回一个新的持有结果的Stream
(不可变性);Stream
操作是延迟执行的(这一点将在后面介绍)。
2. 为什么使用 Stream API
我们在实际开发中,项目中的很多数据都来源于关系型数据库(例如 MySQL、Oracle 数据库),我们使用SQL
的条件语句就可以实现对数据的筛选、过滤等等操作;
但也有很多数据来源于非关系型数据库(Redis
、MongoDB
等),想要处理这些数据,往往需要在 Java 层面去处理。
使用Stream API
对集合中的数据进行操作,就类似于 SQL 执行的数据库查询。也可以使用Stream API
来执行并行操作。简单来说,Stream API
提供了一种高效且易于使用的处理数据的方式。
3. 流式操作的执行流程
流式操作通常分为以下 3 个步骤:
- 创建
Stream
对象:通过一个数据源(例如集合、数组),获取一个流; - 中间操作:一个中间的链式操作,对数据源的数据进行处理(例如过滤、排序等),直到执行终止操作才执行;
- 终止操作:一旦执行终止操作,就执行中间的链式操作,并产生结果。
下图展示了Stream
的执行流程:
接下来我们就按照这 3 个步骤的顺序来展开学习
Stream API
。
4. Stream 对象的创建
有 4 种方式来创建Stream
对象。
4.1 通过集合创建 Stream
Java 8 的java.util.Collection
接口被扩展,提供了两个获取流的默认方法:
default Stream<E> stream()
:返回一个串行流(顺序流);default Stream<E> parallelStream()
:返回一个并行流。
实例如下:
// 创建一个集合,并添加几个元素
List<String> stringList = new ArrayList<>();
stringList.add("hello");
stringList.add("world");
stringList.add("java");
// 通过集合获取串行 stream 对象
Stream<String> stream = stringList.stream();
// 通过集合获取并行 stream 对象
Stream<String> personStream = stringList.parallelStream();
串行流并行流的区别是:串行流从集合中取数据是按照集合的顺序的;而并行流是并行操作的,获取到的数据是无序的。
4.2 通过数组创建 Stream
Java 8 中的java.util.Arrays
的静态方法stream()
可以获取数组流:
static <T> Stream<T> stream(T[] array)
:返回一个数组流。
此外,stream()
还有几个重载方法,能够处理对应的基本数据类型的数组:
public static IntStream stream(int[] array)
:返回以指定数组作为其源的连续IntStream
;public static LongStream stream(long[] array)
:返回以指定数组作为其源的连续LongStream
;public static DoubleStream stream(double[] array)
:返回以指定数组作为其源的连续DoubleStream
。
实例如下:
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class StreamDemo1 {
public static void main(String[] args) {
// 初始化一个整型数组
int[] arr = new int[]{1,2,3};
// 通过整型数组,获取整形的 stream 对象
IntStream stream1 = Arrays.stream(arr);
// 通过字符串类型的数组,获取泛型类型为 String 的 stream 对象
String[] stringArr = new String[]{"Hello", "imooc"};
Stream<String> stream2 = Arrays.stream(stringArr);
}
}
4.3 通过 Stream 的 of()
方法
可以通过Stream
类下的of()
方法来创建 Stream 对象,实例如下:
import java.util.stream.Stream;
public class StreamDemo1 {
public static void main(String[] args) {
// 通过 Stream 类下的 of() 方法,创建 stream 对象、
Stream<Integer> stream = Stream.of(1, 2, 3);
}
}
4.4 创建无限流
可以使用Stream
类下的静态方法iterate()
以及generate()
创建无限流:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
:遍历;public static<T> Stream<T> generate(Supplier<T> s)
:生成。
创建无限流的这种方式实际使用较少,大家了解一下即可。
5. Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。在终止操作时会一次性全部处理这些中间操作,称为“惰性求值”。下面,我们来学习一下常用的中间操作方法。
5.1 筛选与切片
关于筛选和切片中间操作,有下面几个常用方法:
filter(Predicate p)
:接收Lambda
,从流中清除某些元素;distinct()
:筛选,通过流生成元素的hashCode
和equals()
方法去除重复元素;limit(long maxSize)
:截断流,使其元素不超过给定数量;skip(long n)
:跳过元素,返回一个扔掉了前n
个元素的流。若流中元素不足n
个,则返回一个空流。与limit(n)
互补。
我们先来看一个过滤集合元素的实例: