本文是學習Java8,參考JAVA8 IN ACTION這本書,學習整理以及本身的總結,推薦這本書;java
流(Stream)是javaAPI的新成員,它容許你以聲明性方式處理數據集(經過查詢語句來表達而不是臨時編寫一個實現). 此外,流還能夠並行的進行處理,你無須寫任何多線程代碼了.數據庫
首先,咱們以一個例子看下流的使用:下面兩段代碼都是用來返回低熱量的菜餚名稱的, 並按照卡路里排序,一個是用Java 7寫的,另外一個是用Java 8的流寫的。比較一下。不用太擔憂 Java 8代碼怎麼寫,咱們在接下來的幾節裏會詳細解釋。 首先新建一個實體類Dish.java編程
@Data
@AllArgsConstructor
@ToString
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public enum Type { MEAT, FISH, OTHER }
public static final List<Dish> menu =
Arrays.asList( new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 400, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
}
複製代碼
Java8以前的操做:數組
/**
* java7
*/
@Test
public void testJava7(){
ArrayList<Dish> lowcaloriesDishs=new ArrayList<>();
//篩選出低卡路里的菜餚
for (Dish dish:list){
if (dish.getCalories()<400) {
lowcaloriesDishs.add(dish);
}
}
//按照卡路里進行排序
Collections.sort(lowcaloriesDishs, new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
//升序
// return Integer.compare(o1.getCalories(),o2.getCalories());
//降序
return Integer.compare(o2.getCalories(),o1.getCalories());
}
});
//輸出低卡路里的菜品
for (Dish dish:lowcaloriesDishs){
System.out.println(dish.getName()+":"+dish.getCalories());
}
}
複製代碼
獲得結果:season fruit:120
rice:350
複製代碼
在這段代碼中,你用了一個「垃圾變量」 lowcaloriesDishs 。它惟一的做用就是做爲一次 性的中間容器。在Java 8中,實現的細節被放在它本該歸屬的庫裏了。bash
使用Java8的操做:數據結構
/**
* java8
*/
@Test
public void test2(){
List<String> lowDishs=list.stream()
//篩選出低於400的食物
.filter(a->a.getCalories()<400)
// .sorted((a,b)->b.getCalories()-a.getCalories())
//排序
.sorted(Comparator.comparing(Dish::getCalories))
//輸出菜餚名稱
.map(Dish::getName)
.collect(toList());
System.out.println(lowDishs);
}
複製代碼
輸出的結果是:[season fruit, rice]
複製代碼
爲了利用多核架構並行執行這段代碼,你只須要把 stream() 換成 parallelStream() :多線程
/**
* java8 parallelStream
* java8多核架構並行執行這段代碼
*/
@Test
public void test3(){
List<String> lowDishs=list.parallelStream()
.filter(a->a.getCalories()<400)
.sorted(Comparator.comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
System.out.println(lowDishs);
}
複製代碼
一樣獲得結果:[season fruit, rice]
複製代碼
你可能會想,在調用 parallelStream 方法的時候到底發生了什麼。用了多少個線程?對性 能有多大提高?後面會詳細討論這些問題架構
如今,你能夠看出,從軟件工程師的角度來看,新 的方法有幾個顯而易見的好處:編程語言
代碼是以聲明性方式寫的:說明想要完成什麼,而不是說明如何實現一個操做(利用for if 等控制語句).這種方法加上行爲參數化,可讓你很輕鬆的應對變化的需求,你很容易再建立一個代碼版本,利用 Lambda表達式來篩選高卡路里的菜餚,而用不着去複製粘貼代碼ide
你能夠把幾個基礎操做鏈接起來:來表達複雜的數據流水線工做,同時保證代碼清晰可讀.filter 的結果被傳給了 sorted 方法,再傳給 map 方法,最後傳給 collect 方法。
須要注意的是: filter(),sorted(),map(), 返回的都是流(Stream),都是Stream的方法,collect()方法除外.
java8中的集合支持一個新的stream()方法,它會返回一個流,接口定義在 java.util.stream.Stream中.
那麼,流究竟是什麼呢?簡短的定義就是「從支持數據處理操做的源生成的元素序列」。讓咱們一步步剖析這個定義:
元素序列: 就像集合同樣,流提供了一個接口,能夠訪問特定元素類型的一組有序值.由於集合是數據結構,因此它的主要目的是以特定的時間/空間複雜度來存儲訪問元素.但流的目的在於表達計算.
源: 流會使用一個提供數據的源,這些源能夠是 數組,集合,或輸入輸出資源.注意:從有序結合生成的流會保留原有的順序,由列表生成的流,其元素順序也與列表一致.
數據處理操做: 流的數據處理功能相似於數據庫的操做.以及函數式編程語言的經常使用操做.如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操做能夠順序執行,也可並行執行。 此外,流操做有兩個重要的特色。
流水線: 不少流操做自己會返回一個流.這樣多個操做就能夠鏈接起來造成一個更大的流水線.流水線操做能夠當作對數據源進行數據庫式查詢.
內部迭代: 與使用迭代器對集合進行顯示迭代不一樣,流的迭代都是在背後進行的.
讓咱們來看一段可以體現全部這些概念的代碼:
@Test
public void test4() {
List<String> lowCaloricDishesName =
//1.從 menu 得到流(菜餚列表),創建操做流水線
menu.parallelStream()
//2.選出高熱量菜餚
.filter(d -> d.getCalories() > 300)
//3.輸出菜餚名稱
.map(Dish::getName)
//4.只選擇前三個
.limit(3)
//5.將結果保存在另外一個List中
.collect(toList());
System.out.println(lowCaloricDishesName);
}
複製代碼
運行獲得結果:[rice, chicken, prawns]
複製代碼
在本例中,咱們顯示對menu進行stream操做,獲得一個流,數據源是菜餚列表menu,接下來對流進行一系列數據處理操做:filter 、 map 、 limit 和 collect 。除了 collect 以外,全部這些操做都會返回另外一個流,這樣它們就能夠接成一條流水線,因而就能夠看做對源的一個查詢. 最後collect開始處理流水線,並返回一個結果(collect和別的操做不同,它返回的不是一個流而是一個list). 在調用collect以前,沒有任何結果產生,事實上,根本就沒有從menu裏選擇元素.你能夠這麼理解:鏈中的方法調用都在排隊等待,直到調用 collect
圖4-2顯示了流操做的順序: filter 、 map 、 limit 、 collect , 每一個操做簡介以下。
在進一步介紹能對流作什麼操做以前,先讓咱們回過頭來看看Collection API和新的Stream API的思想有何不一樣
粗略的講,流與集合的差別就在於何時進行計算,集合是內存中的數據結構,它包含數據結構中目前全部的值(結合中每一個元素必須先計算出來才能添加到集合中.) (你能夠往集合里加東西或者刪東西,可是無論何時,集合中的每一個元素都是放在內存裏的,元素都得先算出來才能成爲集合的一部分。)
相比之下,流是再概念上固定的數據結構.這個思想就是用戶僅僅從流中提取須要的值,而這些值,在用戶看不見的地方,只會按需生成. 這是一種 生產者--消費者 的關係,從另外一個角度來講,流就想一個延遲建立的集合:只有在消費者要求的時候纔會計算值。 與此相反,集合則是急切建立的。
請注意,和迭代器同樣,流只能遍歷一次,遍歷完以後,咱們就說這個流已經被消費掉了, 你能夠從原始數據源那裏再得到一個新的流來從新遍歷一遍,就像迭代器同樣(這裏假設它是集 合之類的可重複的源,若是是I/O通道就沒戲了)。
例如,如下代碼會拋出一個異常,說流已被消 費掉了:java.lang.IllegalStateException: stream has already been operated upon or closed
/**
* java8屢次使用流異常
*/
@Test
public void test5(){
List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> stream=title.stream();
stream.forEach(a-> System.out.println(a));
//這種狀況下,會報錯:java.lang.IllegalStateException: stream has already
// been operated upon or closed
//由於流已經關閉了,只能使用一次,要想使用,從新獲取流
stream.forEach(b-> System.out.println(b));
}
複製代碼
因此要記得,流只能消費一次!
使用Collection接口須要用戶作迭代(eg:for-each),這稱爲外部迭代;相反,Streams庫使用內部迭代---它不只把迭代作了,還把獲得的流的值存在某個地方,你只要給他一個函數說要幹什麼就能夠了; 相關區別請看一下代碼:
內部迭代時,項目能夠透明的並行處理,或者用更優化的順序進行處理,,這差很少就是Java8引入流的理由了---Streams庫的內部迭代能夠自動選擇一種合適你硬件的數據表示和並行實現流操做能夠分爲中間操做和終端操做; java.util.stream.Stream 中的 Stream 接口定義了許多操做。它們能夠分爲兩大類. 能夠鏈接起來的流操做稱爲中間操做,關閉流的操做稱爲終端操做 下圖展現了這兩類操做:
像filter,map,sort等中間操做會返回一個流; 咱們把代碼改一下,讓每一步操做都返回一個當前處理的值;而後對比一下結果
/**
* 對比每一步的輸出
*/
@Test
public void test6(){
List<String> names=list.stream()
.filter(a->{
System.out.println("filter:"+a.getName());
return a.getCalories()>300;
})
.map(d->{
System.out.println("map:"+d.getName());
return d.getName();
})
.limit(3)
.collect(toList());
System.out.println("names:"+names);
}
複製代碼
輸出的結果:
filter:pork
map:pork
filter:beef
map:beef
filter:chicken
map:chicken
names:[pork, beef, chicken]
複製代碼
是否是和咱們預想的不一樣,這種操做充分利用了流的延遲特性,儘管filter和map是兩個獨立的操做,可是他們合併到同一次遍歷中了,稱爲循環合併;
若是咱們將上面的代碼中的stream換成並行流 parallelStream,看一下運行結果有什麼不一樣
filter:pizza
filter:pork
map:pork
filter:prawns
filter:salmon
filter:rice
map:rice
filter:season fruit
filter:beef
map:beef
filter:chicken
map:chicken
filter:french fries
map:french fries
map:salmon
map:prawns
map:pizza
names:[pork, beef, chicken]
複製代碼
每次運行結果每一步驟的結果也是不一樣的;
終端操做會從流中生成結果,其結果不是任何流中的值,好比:List,Integer,甚至是void; 例如在下面的流水線中,foerach是一個返回void的終端操縱,它會對源中的每道菜應用一個Lambda,把 System.out.println 傳遞給 forEach ,並要求它打印出由 menu 生成的流中的每個 Dish :
menu.stream().forEach(System.out::println);
複製代碼
流是「從支持數據處理操做的源生成的一系列元素」
流利用內部迭代:迭代經過 filter 、 map 、 sorted 等操做被抽象掉了。
流操做有兩類:中間操做和終端操做。
filter 和 map 等中間操做會返回一個流,並能夠連接在一塊兒。能夠用它們來設置一條流 水線,但並不會生成任何結果。
forEach 和 count 等終端操做會返回一個非流的值,並處理流水線以返回結果流中的元素是按需計算的