若想了解java8其它新特性,请到 00-java8常用新特性文章索引 阅读。
都说Java8最重要的两个新特性是lambda表达式和Stream API,lambda表达式在第一篇文章中,我已经介绍过,现在让我们一起继续探索Stream API的神秘面纱。
抛砖引玉
public class Person {
//姓名
private String name;
//年龄
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class StreamAPITest {
List<Person> persons = null;
@Before
public void before() {
persons = Arrays.asList(
new Person("刘一", 25),
new Person("陈二", 12),
new Person("张三", 24),
new Person("李四", 29),
new Person("王五", 22),
new Person("赵六", 15),
new Person("孙七", 16),
new Person("周八", 18)
);
}
/**
* 求所有年龄大于20的人,并且按年龄升序排序
* 不用 Stream API 的做法
*/
@Test
public void test00() {
// 过滤年龄大于20后的列表
List<Person> list = new ArrayList<>();
// 求所有年龄大于20的人,并且按年龄升序排序
for (Person person : persons) {
if (person.getAge() > 20)
list.add(person);
}
// 排序
// Collections.sort(list, Comparator.comparingInt(Person::getAge));//方法引用方式
Collections.sort(list, (p1, p2) -> p1.getAge() - p2.getAge());// lambda表达式方式
// 打印排序后的列表
list.forEach(item -> System.out.println(item));
}
/**
* 求所有年龄大于20的人,并且按年龄升序排序
* Stream API 的做法
*/
@Test
public void test01() {
// 求所有年龄大于20的人,并且按年龄升序排序
List<Person> list = persons.stream()
.filter(person -> person.getAge() > 20)//保留年龄大于20后的元素
.sorted((p1, p2) -> p1.getAge() - p2.getAge())//按年龄升序排序
.collect(Collectors.toList());//终止操作,将流转为List
// 打印排序后的列表
list.forEach(item -> System.out.println(item));
}
}
test00、test01 运行结果都是:
Person{name='王五', age=22}
Person{name='张三', age=24}
Person{name='刘一', age=25}
Person{name='李四', age=29}
test00
中的做法相信很多朋友在工作中都有用过,如果对于列表需要处理的中间操作很多(比如取最大值,计数…),使用test00
的方式就会使得代码很混乱,比较难以维护。
相对而言,test01
使用了 Stream API+lambda表达式,对于集合的操作使用链式方式相继调用,代码逻辑清晰简洁。
通过上面的测试用例,我们初步感受到了Stream API 的便利。像之前的文章一样,我们从 是什么 和 怎么用 两个方面来学习Stream。
篇幅问题,这篇文章介绍 Stream是什么 : Stream 的定义,创建还有各种操作的API。
下一篇文章再介绍 Stream怎么用 : Stream创建、相关API的使用。
1 Stream 是什么?
Stream不是数据结构也不存放任何数据,而是对由数据源所生成的元素序列进行一系列运算,如:查找、过滤、筛选等。
数据源:可能是一个数组,一个集合,一个生成器函数
有点抽象,看看下面的流程图:
注意Stream的特点:
- Stream
不存放
任何数据; - 不会改变源对象,相反,他们会返回一个持有结果的新Stream。
peek
操作除外,只是对流中的元素进行操作,没有返回新流; - 惰性求值:中间操作,并不会立即执行,只有触发终止操作时,才会进行实际的运算;
- 中间操作可以是
0个或者多个
。
下面介绍Stream的创建、中间操作、终止操作相关的API。
2 如何创建Stream
接口或类 | 方法 | 描述 |
---|---|---|
Collection | default Stream stream() default Stream parallelStream() |
将此集合作为数据源,返回一个顺序流 将此集合作为数据源,返回一个并行流 |
Arrays | static Stream stream(T[] array) | 将指定的数组作为数据源,返回一个顺序流 |
Stream | static Stream of(T… values) static Stream iterate(final T seed, final UnaryOperator f) static Stream generate(Supplier s) |
底层调用的是Arrays.stream(T[] array),将参数数组作为数据源,返回一个顺序流 创建无限流,入参是初始元素和UnaryOperator函数式接口,返回一个有规律的无限顺序流 创建无限流,入参是Supplier,返回一个无规律的无限顺序流,其中每个元素由提供的Supplier生成,适用于生成恒定流、随机元素流 |
中间操作和终止操作,涉及到一些java内置的函数式接口,很多朋友可能不是很熟悉,这里把常用的列出来。
Java常用的内置函数式接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 | T | void | 对给定的 T 类型的参数,进行操作, 核心方法: void accept(T t); |
Supplier 供给型接口 | 空参 | T | 获取一个 T 类型的对象 核心方法: T get(); |
Function<T, R> 函数型接口 | T | R | 对 T 类型的参数进行操作,返回 R 类型的结果 核心方法: R apply(T t); |
Predicate 断定型接口 | T | boolean | 判断 T 类型的参数,是否满足某约束,返回 boolean 值 核心方法: boolean test(T t); |
BiFunction<T, U, R> | T, U | R | 对类型为 T, U 参数应用操作, 返回 R 类型的结果。 核心方法为 R apply(T t, U u); |
UnaryOperator (Function子接口) |
T | T | 对类型为T的对象进行一元运算, 并返回T类型的结果。 核心方法为 T apply(T t); |
BinaryOperator (BiFunction 子接口) |
T, T | T | 对类型为T的对象进行二元运算, 并返回T类型的结果。 核心方法为 T apply(T t1, T t2); |
BiConsumer<T, U> | T, U | void | 对类型为T, U 参数应用操作。 核心方法为 void accept(T t, U u) |
ToIntFunction ToLongFunction ToDoubleFunction |
T | int/long/double | T 类型的参数, 返回int\long\double 类型的结果, 核心方法为 int\long\double applyAsInt(T value); |
IntFunction LongFunction DoubleFunction |
int/long/double | R | 参数分别为int、long、double 类型的函数, 返回 R 类型的结果, 核心方法: R apply(int\long\double value); |
3 Stream 中间操作
多个中间操作可以连接起来形成一个流水线,中间操作并不会立即执行,只有触发终止操作时,才会进行实际的运算。
Stream 接口中提供的中间操作API:
方法 | 描述 |
---|---|
Stream distinct() | 返回由不同元素组成的流,根据元素的equals()方法去重 |
Stream filter(Predicate<? super T> predicate); | 过滤,排除掉没有匹配 predicate函数式接口的元素 |
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) | 通过入参Function函数式接口将流中的每个元素都换成单独一个流,然后把转换后的所有流连接成一个流 |
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper) | 通过入参Function函数式接口将流中的每个元素都换成单独一个DoubleStream(流中元素的类型是Double),然后把转换后的所有DoubleStream连接成一个DoubleStream |
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) | 通过入参Function函数式接口将流中的每个元素都换成单独一个IntStream(流中元素的类型是Integer),然后把转换后的所有IntStream连接成一个IntStream |
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper) | 通过入参Function函数式接口将流中的每个元素都换成单独一个LongStream(流中元素的类型是Long),然后把转换后的所有LongStream连接成一个LongStream |
Stream limit(long maxSize) | 限制流的元素数量,截断流,使其元素不超过给定数量 |
Stream map(Function<? super T, ? extends R> mapper) | 通过入参Function函数式接口将流中的每个T类型元素映射成一个R类型的新元素 |
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper) | 通过入参ToDoubleFunction函数式接口将流中的每个T类型元素映射成一个Double类型的新元素 |
IntStream mapToInt(ToIntFunction<? super T> mapper) | 通过入参ToIntFunction函数式接口将流中的每个T类型元素映射成一个Integer类型的新元素 |
LongStream mapToLong(ToLongFunction<? super T> mapper) | 通过入参ToLongFunction函数式接口将流中的每个T类型元素映射成一个Long类型的新元素 |
Stream peek(Consumer<? super T> action) | 通过入参Consumer函数式接口将流中的每个T类型元素进行操作,如给元素赋值,每个元素引用没变 |
Stream skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。如果此流包含的元素少于n,则返回空流 |
Stream sorted() | 自然排序后返回一个新的流,如果元素不是Comparable类型,则抛出ClassCastException |
Stream sorted(Comparator<? super T> comparator) | 根据给定 Comparator 排序后,返回一个新流 |
4 Stream 终止操作
终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:boolean、long、void… 。
Stream 接口中提供的终止操作API:
方法 | 描述 |
---|---|
boolean allMatch(Predicate<? super T> predicate) | 如果流中所有元素都匹配 predicate 或者是空流,返回true,否则返回false |
boolean anyMatch(Predicate<? super T> predicate) | 如果流中任意一个元素匹配 predicate返回true,否则返回false,空流返回false |
<R, A> R collect(Collector<? super T, A, R> collector) | 接收一个Collector实例,将流中元素收集成另外一个数据结构 |
long count() | 返回流中元素总数 |
Optional findAny() | 返回任意一个元素,用Optional描述,如果是空流,返回空的Optional(Optional.empty),值为null |
Optional findFirst() | 返回流中第一个元素,用Optional描述,如果是空流,返回空的Optional(Optional.empty),值为null |
void forEach(Consumer<? super T> action) | 对此流的每个元素执行 Consumer 操作 |
Optional max(Comparator<? super T> comparator) | 根据给定的Comparator,返回流中最大的元素,用Optional描述,如果是空流,返回空的Optional(Optional.empty),值为null |
Optional min(Comparator<? super T> comparator) | 根据给定的Comparator,返回流中最小的元素,用Optional描述,如果是空流,返回空的Optional(Optional.empty),值为null |
boolean noneMatch(Predicate<? super T> predicate) | 如果流中没有元素匹配 predicate 或者是空流,返回true,否则返回false |
Optional reduce(BinaryOperator accumulator) | 第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。最终得到用Optional描述的T类型的结果。 |
T reduce(T identity, BinaryOperator accumulator) | 第一次执行时,accumulator函数的第一个参数identity,第二个参数为流中元素的第一个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第二个元素;依次类推。最终得到T类型的结果。 |
Object[] toArray() | 返回包含此流元素的数组 |
5 Stream API 分类
中间操作:
无状态:指元素的处理不受其他元素的影响;
有状态:指该操作只有拿到所有元素之后才能继续下去。
终止操作:
非短路操作:指必须处理所有元素才能得到最终结果;
短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
分类 | 操作 | |
---|---|---|
中间操作 | 无状态(不受其他元素的影响) | filter() flatMap() flatMapToDouble() flatMapToInt() flatMapToLong() map() mapToDouble() mapToInt() mapToLong() peek() |
中间操作 | 有状态(该操作只有拿到所有元素之后才能继续下去) | distinct() limit() skip() sorted() sorted(comparator) |
终止操作 | 非短路操作(必须处理所有元素才能得到最终结果) | collect() count() forEach() max() min() reduce() toArray() |
终止操作 | 短路操作(遇到某些符合条件的元素就可以得到最终结果) | allMatch() anyMatch() findAny() findFirst() noneMatch() |
Stream的创建、中间操作、终止操作相关的API就介绍到这,下篇文章,一起看看这些API使用的demo。