【Java 8 学习笔记 (1)】 Stream

这段时间正好接触到Java8中的Stream,就想写点东西来简单记录下。

像我们学习其他知识一样,关于Stream,首先得弄清楚以下几点:

  1. What - 它是个什么东西(类?接口?),它在哪个包下,关系背景又怎样(这是只是有没有继承关系)
  2. Why - 为什么要用它,好处是什么…
  3. How - 怎么使用其实就没那么难了

接下来,就围绕上述问题来开始我们今天的讨论…论述如有不当之处,欢迎大家批评指正。

What

JDK文档有其的官方定义:

A sequence of elements supporting sequential and parallel aggregate operations.

官方这定义可谓精辟,首先,Stream代表的是一组有序的元素集合,然后支持串行和并行的聚集操作这一句要稍微解释下,这里的串行和并行指的是单任务和多任务去操作Stream(Stream的并行操作依赖于Java 7中引入的Fork/Join框架),从而加速处理过程,发挥多核处理器的优势。

Stream接口位于java.util.stream, 并且继承于BaseStream(父亲)。在java.util.stream中,我们可以发现她的3个兄弟姐妹,例如,DoubleStream, IntStream, LongStream,这里就不详细介绍其家人了,其实就是针对double,int,long,3种类型的流做了一些方法的封装,完全是可以通过原生Stream实现的。

Why

任何一个新生的接口都有其存在价值和意义,Stream也不例外。几乎任何Java应用都不可避免的会用到创建及处理集合数据。首先我们来看下在数据库中,常见的处理集合的方式,例如:

1
2
3
SELECT name, wechat_account, MAX(money)
FROM tb_girls as girl
WHERE girl.height >= 168

如上,我们需要找到其中身高168cm并且钱最多以上的MM,这里我们不需要写代码去实现查找Max的实现逻辑,只需要用Maxgirl.height>=168表述我们需要的就可以了。

那么问题来了,我们是否可以对Java中的集合(例如, List<Girl>)进行类似操作呢?这里简单说明一下,并不是说Java里通过集合的操作不能实现上述功能,而是在于能否使用类似的代码编写的形式。

绕了一大圈,我们今天的主角Stream该登场了,它为Java的集合类提供了一种声明式的方式来操作数据。接下来我们再用一个例子来具体实现下,现已知一个Person的对象集合 allPersons,我们想要取出所有人中白富美的微信号,而且是按照资产数值由高到低排序:

In Java 7 or lower

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<Person> allGirls = new ArrayList<>();
for(Person p: allPersons){
if(p.getGender() == Gender.FEMALE){
allGirls.add(p);
}
}
Collections.sort(allGirls, new Comparator(){
public int compare(Person p1, Person p2){
return p1.getMoney().compareTo(p2.getMoney());
}
});
List<String> whiteRichBeautiful = new ArrayList<>();
for(Person p: allGirls){
whiteRichBeautiful.add(p.getWeChat());
}

In Java 8 by using Stream

1
2
3
4
5
List<String> whiteRichBeautiful = allPersons.stream()
.filter(p -> p.getGender() == Gender.FEMALE)
.sorted(comparing(Person::getMoney).reverse())
.map(Person::getWeChat())
.collect(Collectors.toList());

机智如我的你,是不是已经发现这种编码方式很简洁,当然它也很高效(这里的高效并不会做很详细的解释),这里我引用来自另一篇文章的解释:

在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

How

目前为止,我们对Stream存在的原因和优点有个大致的理解。接下来来看看如何去创建及使用Stream。

创建Stream

这里还是只介绍2种最常见的创建方式:

  1. Stream的静态方法
1
2
3
4
5
6
7
8
9
10
11
12
//方法1. Stream.of()
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
Stream<String> stringStream = Stream.of("Hello World");
//方法2. Stream.generator()
Stream.generator(new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);

对于方法1,有2个重载方法;对于方法2,Stream则是通过给定的Supplier接口(这个接口会在后续的文章中给大家介绍)来创建,最后的2行代码只是使用了lambda表达式和方法引用来简化代码。

  1. Collection子类的stream()方法

上文的例子中就演示了如何从List<Person>对象中通过allPersons.stream()获取其Stream对象。从Collection的源码中就可以找到 stream()方法的定义及实现(实现??? 对的,没错,当然这也是Java 8里新加的特性: Default Method, 所以它只需要定义在Collection中,而不需要在其每个子类中完成实现),该方法的返回值类型就是Stream

操作Stream

在一个Stream对象被创建之后,接下来就是对其进行数据操作。一个数据流(Stream pipeline)是由数据源,若干中间方法(intermediate operation),一个汇聚方法(terminal operation)组成.

  • 中间方法(intermediate operation)主要作用是完成数据的映射/过滤/排序等操作(形象点说就像数据流从一个管道,流入下一个管道,但是请注意数据流在流经中间管道时候自身并不会发生变化,只是记录了每个管道所对应的操作),这些操作的返回值都是Stream, 这就为链式(声明式)操作提供了基础。常见的操作有:filter(),map(),sorted(),distinct(),limit(),skip()
  • 汇聚方法(terminal operation)主要作用则是真正去处理和操作 (traverse,这里实在想不出一个很合适的词来形容,)整个Stream,例如,计算总值,查找最大值,将处理之后Stream中的集合元素封装成新的集合返回。在terminal operation后,Stream pipeline将会被关闭。常见的操作有:count(),foreach(),collect(),max(),allMatch(),anyMatch()

上述各种函数名是不是与SQL中的某些函数有似曾相识的感觉呢,也很容易理解各个方法的作用,这里就不再一一赘述。此外,操作方法的参数与大家平时常见的也有所不同,就拿上面例子中filter的参数来说p -> p.getGender() == Gender.FEMALE, 这个就是传说中的lambda表达式(请切记,lambda表达式只是一种表达方式,其本质实际上为一个函数式接口Functional Interface,它只是将这个接口的实现用lambda表达式的形式呈现出来),也是Java 8里一个添加的重要的特性。 可以看出,lambda表达式可以让我们更加灵活的去操作Stream,并且是我们的代码看上去更加简洁。

小结

本文旨在对Stream做一个入门介绍,涵盖了What,Why,How。当然,Stream所包含的东西远不止这些,建议大家有空还是去读一下官方文档。至此,相信大家也已经能够读懂之前查找白富美微信号的那段代码了吧。

下一篇文章将讨论Java 8的另一个重要内容 - Functional Interface

参考文献

  1. JDK Doc
  2. Processing Data with Java SE 8 Streams, Part 1
  3. Java 8 中的 Streams API 详解
  4. Java8初体验(二)Stream语法详解