公子奇带你进入Java8流的世界(一)
在说流之前,我们先来看看集合,为什么呢?作为Java8中的新成员,它和集合有很多相似之处,同时它们也是可以互相转化的。集合不仅仅是Java语言,任何一门高级开发语言都有集合的概念,集合顾名思义,就是很多数据集放在一起,它算是一个容器,同时我们也可以使用泛型来限制集合中的数据类型。
一、流是什么
流作为是Java8的新成员,它允许我们以声明性的方式来处理数据集合。我们可以把它看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,这就使得我们无需编写任何多线程代码。在后续中我们再来详细说说流和流的并行化。
话不多说,我们用一段代码来直观比较Java8前后的区别:
1 package com.hz; 2 3 import java.util.*; 4 5 import static java.util.stream.Collectors.toList; 6 7 public class StreamDemo { 8 public static void main(String[] args) { 9 List<Police> polices = Arrays.asList( 10 new Police("P001", "余警官", 27, "浙江"), 11 new Police("P002", "李警官", 32, "安徽"), 12 new Police("P003", "程警官", 25, "安徽"), 13 new Police("P004", "杨警官", 35, "浙江"), 14 new Police("P005", "张警官", 31, "上海"), 15 new Police("P006", "王警官", 42, "浙江"), 16 new Police("P007", "赵警官", 31, "浙江"), 17 new Police("P008", "刘警官", 49, "浙江"), 18 new Police("P009", "周警官", 32, "浙江") 19 ); 20 21 //问题:将以上集合中的民警,大于30岁的民警筛选出来,并按照年龄排序,将筛选结果的民警姓名存储到另一个集合中 22 23 /*********************************** Java7实现以上问题 ************************************************/ 24 List<Police> tempList = new ArrayList<>(); 25 for (Police police : polices) { 26 if (police.getPoliceAge() > 30) { 27 tempList.add(police); 28 } 29 } 30 Collections.sort(tempList, new Comparator<Police>() { 31 @Override 32 public int compare(Police o1, Police o2) { 33 return Integer.compare(o1.getPoliceAge(), o2.getPoliceAge()); 34 } 35 }); 36 List<String> tempPoliceNameList = new ArrayList<>(); 37 for (Police police : tempList) { 38 tempPoliceNameList.add(police.getPoliceName()); 39 } 40 System.out.println(tempPoliceNameList); 41 42 System.out.println("-------------------------------- 分割线 ---------------------------------------"); 43 44 /*********************************** Java8实现以上问题 ************************************************/ 45 List<String> tempPoliceNameStream = polices.stream().filter(p -> p.getPoliceAge() > 30) 46 .sorted(Comparator.comparing(Police :: getPoliceAge)) 47 .map(Police :: getPoliceName) 48 .collect(toList()); 49 System.out.println(tempPoliceNameStream); 50 51 //说明:若想并行执行,在Java8中,只需要将polices.stream() 改为polices.parallelStreamm() 52 } 53 } 54 55 结果: 56 [张警官, 赵警官, 李警官, 周警官, 杨警官, 王警官, 刘警官] 57 -------------------------------- 分割线 --------------------------------------- 58 [张警官, 赵警官, 李警官, 周警官, 杨警官, 王警官, 刘警官]
从以上一段代码中,我们可以看出:
1、代码是以声明性方式编写。即想要完成的工作,而非如何完成。
2、可以使用操作链。filter之后的方法可以直接点,直到完成。
由上,我们可以先简单的总结下使用流的好处:
1、声明性:更简洁易读。
2、可复用:更加灵活。
3、可并行:性能更好。
二、流的介绍
上面我看了流和集合的简单比较,那么到底流是什么呢?我们可以简单说明为“从支持数据处理操作的源生成的元素序列”。我们将这句话分开来解析:
①、元素序列:它就如何集合一样,可以访问特定元素类型的一组有序值。但它与集合是不同的,集合是一种数据结构,它的主要目的是在一定时间和空间上存储数据。而流主要用来计算。他们本质上是不同的。
②、源:即源头,流在处理数据时,这个数据的源头,例如:集合可以是个源,文件也可以是个源。
③、数据处理操作:流在处理数据时类似我们操作数据库,如:filter/map/sort等。流在处理数据时,可顺序执行也可并行执行。
流在操作中具有两个很明显的特征:
1、流水线。即流的操作返回的还是一个流,如此多个操作就可以一直往后链接,从而形成一个流水线。
2、内部迭代。流在处理时,我们是看不到处理过程的,它是在背后执行的。我们可以回看上一节中,民警在筛选/排序/映射到后面的截取/转换等如何完成的,我们无法看到执行过程。
三、集合与流比对
在Java8中集合和流是可以互相转化的,但从数据上来看,集合是可以不断的遍历,而流只可以遍历一次,一次遍历结束后,即代表该条流完成,若想再次处理,则需要重新建立一个流对象。若我们对一个已经完成的流再次处理,则会抛出异常。
package com.hz; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class StreamDemo2 { public static void main(String[] args) { List<String> list = Arrays.asList("Java", "JavaScript", "Python"); Stream<String> stream = list.stream(); stream.forEach(System.out :: println); System.out.println("------ 分割线 --------"); stream.forEach(System.out :: println); } } 结果: Java JavaScript Python ------ 分割线 -------- Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at com.hz.StreamDemo2.main(StreamDemo2.java:18) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
根据结果我们可知道,流已经被操作完或关闭了。当我们想再操作该流时,则异常。
我们上一节说到流是内部迭代的,集合也是有迭代的,只是集合一般我们都是外部迭代,那么这两种迭代方式有什么不同呢?
package com.hz; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import static java.util.stream.Collectors.toList; public class StreamDemo3 { public static void main(String[] args) { List<Book> books = Arrays.asList( new Book("Java入门", "张**"), new Book("JavaScript入门", "李**"), new Book("Python入门", "王**") ); List<String> booksName = new ArrayList<>(); //************ 1- 使用for-each做外部迭代 for (Book book : books) { booksName.add(book.getBookName()); } System.out.println(booksName); System.out.println("--------- 分割线 ---------"); booksName.clear(); //************ 2- 使用迭代器(背后迭代器)做外部迭代 Iterator<Book> bookIterator = books.iterator(); while (bookIterator.hasNext()) { Book book = bookIterator.next(); booksName.add(book.getBookName()); } System.out.println(booksName); System.out.println("--------- 分割线 ---------"); booksName.clear(); //************ 1- 使用流 内部迭代 booksName = books.stream().map(Book :: getBookName).collect(toList()); System.out.println(booksName); } static class Book { private String bookName; private String bookAuth; public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getBookAuth() { return bookAuth; } public void setBookAuth(String bookAuth) { this.bookAuth = bookAuth; } public Book(String bookName, String bookAuth) { this.bookName = bookName; this.bookAuth = bookAuth; } } } 说明:由上代码我们可以看到1/2两种迭代的过程我们是知道的,而3我们无法看到迭代细节。
四、流基本操作介绍
从上一节,我们可以看到一段代码
polices.stream().filter(p -> p.getPoliceAge() > 30) .sorted(Comparator.comparing(Police :: getPoliceAge)) .map(Police :: getPoliceName) .collect(toList());
对于这段代码我们可以将其分为两个部分或两种操作:
1、从stream方法之后,filter / sorted / map 返回的都为一个流,组成一个流水线。
2、collect方法执行后流即关闭,并返回一个非流对象。
对于这两个部分,我们将第一种操作称为中间操作,第二种操作称为终端操作。
在中间操作中,代码的真正逻辑并没有执行,只有当遇到了终端操作,之前的中间操作才开始执行,知道结束并关闭流。
由这些,我们也可以以总结下流的使用主要包含了三件事,即首先需要一个数据源用来执行查询,再次需要一个中间操作链形成一条流的流水线,最后需要一个终端操作来执行流水线并返回结果。