1. 程式人生 > >第四章(1) 流Stream介紹

第四章(1) 流Stream介紹

1.使用流的好處 

 流是Java API的新成員,它允許你以宣告性方式處理資料集合(通過查詢語句來表達,而不是臨時編寫一個實現,例如你要在集合中篩選一個紅色的蘋果,你可以用類似於sql式的查詢結構來說明你要幹什麼就可以了,而無需想著如何的去實現它,比如使用for迴圈+if判斷)。就現在來說,你可以把它們看成遍歷資料集的高階迭代器。此外,流還可以透明地並行處理,你無需寫任何多執行緒程式碼了!

我們將使用下面的程式碼來說明本章的內容:

/*
 * 菜品類
 * */
public class Dish {

	private final String name; //名字
	private final boolean vegetarian; //是否為素菜
	private final int calories;//蘊含熱量 
	private final Type type;//菜品種類
	public Dish(String name, boolean vegetarian, int calories, Type type) { 
		this.name = name; 
		this.vegetarian = vegetarian; 
		this.calories = calories; 
		this.type = type; 
	}
	public String getName() { 
		return name;
	}
	public boolean isVegetarian() { 
		return vegetarian;
	} 
	public int getCalories() { 
		return calories; 
	}
	public Type getType() {
		return type; 
	} 
	@Override public String toString() { 
		return name;
	}
	public enum Type { MEAT, FISH, OTHER }
}

然後在建立一個菜品列表:

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, 300, Dish.Type.FISH), 
   new Dish("salmon", false, 450, Dish.Type.FISH) );

我現在有個需求,就是篩選出熱量大於400的菜品,按熱量大小排序,將菜品名稱提取出來形成一個List,在java8之前,我們只能這樣做:

List<Dish> heightDishList = new ArrayList<Dish>();
		//篩選菜品熱量大於400的菜品
		for(Dish dish : menu){
			if(dish.getCalories()>400){
				heightDishList.add(dish);
			}
		}
		//給菜品排序
		heightDishList.sort(new Comparator<Dish>() {

			@Override
			public int compare(Dish o1, Dish o2) {
				// TODO Auto-generated method stub
				return o1.getCalories()-o2.getCalories();
			}
		});
		//提取菜品名稱
		List<String> heightDishNameList = new ArrayList<>();
		for(Dish dish : heightDishList){
			heightDishNameList.add(dish.getName());
		}
		//列印菜品
		for(String string : heightDishNameList){
			System.out.println(string);
		}

而當我們使用java8的stream,我們就可以這樣搞:

List<String> heightDishNameDishs = menu.stream().filter(d1 -> d1.getCalories()>400)//熱量大於400
				.sorted(Comparator.comparing(Dish::getCalories))//排序
				.map(Dish::getName)//提取名稱
				.collect(Collectors.toList());//形成一個List
		
		//列印
		heightDishNameDishs.stream().forEach(System.out::println);

我們發現這樣做的好處有:

•程式碼是以宣告性方式寫的:說明想要完成什麼(篩選熱量低的菜餚)而不是說明如何實現一個操作(利用迴圈和if條件等控制流語句)。你在前面的章節中也看到了,這種方法加上行為引數化讓你可以輕鬆應對變化的需求:你很容易再建立一個程式碼版本,利用Lambda表示式來篩選高卡路里的菜餚,而用不著去複製貼上程式碼。

•你可以把幾個基礎操作連結起來,來表達複雜的資料處理流水線(在filter後面接上sorted、map和collect操作,如圖4-1所示),同時保持程式碼清晰可讀。filter的結果被傳給了sorted方法,再傳給map方法,最後傳給collect方法。

為了利用多核架構並行執行這段程式碼,你只需要把stream()換成parallelStream():

List<String> heightDishNameDishs = menu.parallelStream().filter(d1 -> d1.getCalories()>400)//熱量大於400
				.sorted(Comparator.comparing(Dish::getCalories))//排序
				.map(Dish::getName)//提取名稱
				.collect(Collectors.toList());//形成一個List

總結一下,Java 8中的Stream API可以讓你寫出這樣的程式碼:

•宣告性——更簡潔,更易讀

•可複合——更靈活

•可並行——效能更好

2.流簡介

      要討論流,我們先來談談集合,這是最容易上手的方式了。Java 8中的集合支援一個新的stream方法,它會返回一個流(介面定義在java.util.stream.Stream裡)。你在後面會看到,還有很多其他的方法可以得到流,比如利用數值範圍或從I/O資源生成流元素 。

    那麼什麼是流?流就是從支援資料處理操作的源生成的元素序列。這是書上給的定義,簡直不知所云,後面給的解釋也是不知所云。但我還是根據自己的理解,去儘量理解這些概念性的東西,實在理解不了或者理解錯了也無所謂,因為這並影響我們使用:

1.什麼是元素序列?對比與java的集合框架是講究資料的儲存與訪問,那麼流的這個元素序列就是用於表達計算的一種東東。比如你前面見到的filter、sorted和map。

2.什麼是源?流會使用一個提供資料的源,如集合、陣列或輸入/輸出資源。請注意,從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。

3.資料處理操作——流的資料處理功能支援類似於資料庫的操作,以及函數語言程式設計語言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以順序執行,也可並行執行。

此外流還有兩個重要的特點:流水線和內部迭代

很多流操作本身會返回一個流(比如filter,map等操作),這樣多個操作就可以連結起來,形成一個大的流水線

內部迭代——與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。

3.一個流只能被消費一次

4.內部迭代和外部迭代

       所謂外部迭代就是我們開篇所舉例的那種使用for迴圈來實現的例子,而內部迭代就是之後使用stream流優化的例子,你會發現使用stream的例子並沒有什麼顯示的迭代,因為它在內部已經幫你做好了。

      關於內部迭代的好處,書的作者給了一個有趣的比方:

也就是說stream的內部迭代可以幫助我們並行優化處理資料!

5.流操作

流操作分為兩類 

一是中間操作:

諸如filter或sorted等中間操作會返回另一個流。這讓多個操作可以連線起來形成一個查詢。重要的是,除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理——它們很懶。這是因為中間操作一般都可以合併起來,在終端操作時一次性全部處理。

二是終端操作:

6.使用流

總而言之,流的使用一般包括三件事:

•一個數據源(如集合)來執行一個查詢;

•一箇中間操作鏈,形成一條流的流水線;

•一個終端操作,執行流水線,並能生成結果。