学习 Java 的过程中,我们已经接触到了各种各样的流:输入流、输出流、文件输入流、文件输出流等等,但是在 Java8 中又提出了一个流的概念,这篇文章就来讲讲 Java8 中的流,看看它与以前了解的流究竟有什么关系。
1. 什么是流
在 《Java8实战》 这本书中,流的定义是
从支持数据处理操作的源生成的元素序列
按照我的理解,流就是一种在 Java8 中新提出的数据集合。这个新的数据集合提供一系列的 API ,可以方便我们对集合进行数据的筛选、转换等操作。
2. 流之初体验
阅读应该是人生活中就像阳光、空气和水一样自然而然的存在。
我就拿书作为例子来讲讲 Java8 中的流。首先,我们先来看看流到底是怎么样的,以及流的作用。定义一个 Book 的实体类:
publicclassBook{
//书名
privateString name;
//作者
privateString author;
//价格
privatedoubleprice;
//出版社
privateString publish;
//出版年份
privateintpublishYear;
publicBook(String name,String author,doubleprice,String publish,intpublishYear){
this.name = name;
this.author = author;
this.price = price;
this.publish = publish;
this.publishYear = publishYear;
}
/*省略set和get方法*/
}
假使我们现在有很多本书,给我们的要求是找出这些书中所有在 2000 年以前出版的书的名字。这个任务看起来一点都不难啊,老司机一下子就能写好:
List books = Arrays.asList(
newBook("书名一","作者甲",19.8,"一号出版社",1995),
newBook("书名二","作者乙",99.8,"一号出版社",2016),
newBook("书名三","作者乙",9.9,"一号出版社",1994),
newBook("书名四","作者甲",21.3,"一号出版社",1998),
newBook("书名五","作者乙",30.2,"一号出版社",1999),
newBook("书名六","作者丙",15.7,"二号出版社",2000),
newBook("书名七","作者甲",49.0,"二号出版社",2007),
newBook("书名八","作者丁",72.0,"二号出版社",2012),
newBook("书名九","作者丙",98.0,"二号出版社",2015),
newBook("书名十","作者丁",100.0,"二号出版社",2014)
);
List bookNames = newArrayList<>();
for(Book book : books) {
if(book.getPublishYear() <2000) {
bookNames.add(book.getName());
}
}
但是使用流的写法可以更简便,一句话就能搞定:
List bookNamesNew = books.stream()
.filter(b -> b.getPublishYear()
.map(Book::getName)
.collect(toList());
这里用到了 Lambda 写法,不会的可以参考之前的文章。这个任务比较简单,所以并不能很好说明流的好处,但是这只是为了简单的介绍一下流可以做什么,现在大家应该已经有一个初步的印象了。等了解完流其他的功能之后,更加能体会到流的好用之处。
3. 流之再体验
使用流来处理问题的时候,一般有三个步骤:
获取流,像上面的 books.stream() 返回的是一个 Stream 的流对象
执行 中间操作 对数据进行处理,上面的 fliter() 和 map() 都是对流中的数据进行处理操作
执行 终端操作 获得预期的返回结果, collect(toList()) 根据我们的预期返回 List 类型的数据
Java8 中的流有一个很重要的特性就是 并行处理 ,对于数据的处理不是一个步骤一个步骤地处理,而是像流水线一样处理,这个并行处理并不需要我们处理,Java 内部就会帮我们实现。只需要更改一下获取流对象的方法就行,将 stream() 方法改为 parallelStream() 方法即可。上面代码中的 filter() 、 map() 和 collect() 这几个操作其实是并行处理的。这就省去了我们代码优化啊、实现多线程啊一系列工作。
3.1 中间操作
中间操作是对流对象进行处理的操作,中间操作会返回一个流对象,这样多个中间操作可以连起来,形成流水线。下面介绍一下主要的一些中间操作:
3.1.1 筛选操作
源码定义:
Stream filter(Predicate<?superT> predicate);
通过传入一个 Predicate 类型的 Lambda 表达式,对流对象中的数据进行筛选。
写法示例:
List books1 = books.stream()
.filter(b -> b.getPublishYear()
.collect(toList());
3.1.2 去重操作
源码定义:
Stream distinct();
对流对象中的数据进行去重,其判断对象是否一样调用的是 equals() 方法。
写法示例:
List books2 = books.stream()
.filter(b -> b.getPublishYear()
.distinct()
.collect(toList());
3.1.3 限制返回个数
源码定义:
Stream limit(longmaxSize);
对流操作返回的结果中的个数进行限制,按顺序返回前 maxSize 个元素,若元素个数不足 maxSize 则返回所有元素。
写法示例:
List books3 = books.stream()
.filter(b -> b.getPublishYear()
.limit(3)
.collect(toList());
3.1.4 跳过操作
源码定义:
Stream skip(longn);
将流对象中前 n 个元素去除,并返回一个流对象,如果元素个数不足 n ,则返回一个数据为空的一个流对象。
写法示例:
List books4 = books.stream()
.filter(b -> b.getPublishYear()
.skip(3)
.collect(toList());
3.1.5 映射操作
源码定义:
Streammap(Function<?superT , ? extends R> mapper);
通过传入一个 Function 类型的函数式接口,完成流对象中元素的类型转换功能。
写法示例:
List books5 = books.stream()
.map(Book::getName)
.collect(toList());
3.1.6 排序操作
源码定义:
Stream sorted();
Stream sorted(Comparator<?superT> comparator);
有默认排序和自定义排序,自定义排序的话,传入一个 Comparator 类型的函数式接口,按照自己预期的情况进行排序,返回排序之后的流对象。
写法示例:
List books6 = books.stream()
.sorted()
.collect(toList());
3.2 终端操作
终端操作将流对象转换成我们所期望的结果,终端操作是流水线上的最后一个操作,一般终端操作都不会像中间操作一样返回一个流对象。因为流对象像迭代器一样只能遍历一次,所以当流对象遇到终端操作之后,整个流水线就结束了。
3.2.1 返回集合
源码定义:
Rcollect(Supplier supplier,
BiConsumer accumulator,
BiConsumer combiner);
通过传入参数来确定返回的结果类型,像之前一直用的 collect(toList()) 就是说将对象流转换成 List 类型的集合, toList() 是在 java.util.stream.Collectors 中定义好的, Java8 已经为我们准备好了一些常用的转换规则,这可以在 java.util.stream.Collectors 中找到。
写法示例:
List books1 = books.stream()
.filter(b -> b.getPublishYear()
.collect(toList());
3.2.2 查找是否至少存在一个
源码定义:
booleananyMatch(Predicate<?superT> predicate);
判断流对象中是否存在满足 Predicate 类型的元素,如果存在则返回 true ,否则返回 false 。
写法示例:
booleanbooks7 = books.stream()
.anyMatch(b -> b.getPublishYear()
3.2.3 查找第一个元素
源码定义:
Optional findFirst();
```
返回流对象中第一个元素。
**写法示例**:
``` java
Book books8 = books.stream()
.filter(b -> b.getPublishYear()
.findFirst().get();
3.2.4 返回元素个数
源码定义:
longcount();
返回流对象中元素的个数。
写法示例:
longbooks9 = books.stream()
.filter(b -> b.getPublishYear()
.count();
4. 小结
其实这篇文章就只是简单的介绍了一下 Java8 中流的内容,本来想把 Java8 中定义的关于流的 API都讲一下的,但是发现这个量有点大,所以就挑了一些比较有代表性的 中间操作 和 终端操作 讲一下,更多的内容还是得靠自己发掘了。