Java8新特性之Stream詳解一
寫了Lambda表示式的部落格,怎麼能少了對Stream的學習呢!接下來就將我所理解的Stream分享給大家,歡迎指出不足之處...
一、為什麼要使用Stream?Stream是什麼?在Java中該怎麼用呢?
(1)為什麼要使用Stream?
Java 8 中的 Stream 是對集合(Collection)物件功能的增強,它專注於對集合物件進行各種非常便利、高效的聚合操作,或者大批量資料操作 (bulk data operation)。Stream API 藉助於同樣新出現的 Lambda 表示式,極大的提高程式設計效率和程式可讀性。
(2) Stream是什麼?
首先,Stream 不是集合元素,它不是資料結構並不儲存資料,它是有關演算法和計算的,它更像一個高階版本的 Iterator。原始版本的 Iterator,使用者只能顯式地一個一個遍歷元素並對其執行某些操作;高階版本的 Stream,使用者只要給出需要對其包含的元素執行什麼操作,比如 “過濾掉長度大於 10 的字串”、“獲取每個字串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的資料轉換。
Stream 就如同一個迭代器(Iterator),單向,不可往復,資料只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。
(3) Stream在Java中如何用呢?
當我們使用Stream流時,一般包含三個步驟:獲取一個數據源(source)→ 資料轉換 → 執行操作獲取想要的結果,每次轉換原有 Stream 物件不改變,返回一個新的 Stream 物件(可以有多次轉換),這就允許對其操作可以像鏈條一樣排列,變成一個管道。
有多種方式產生一個流:
① 從Collection和陣列中產生一個流:Collection.stream() Collection.parallelStream()
Arrays.stream(T array) Stream.of(元素)
② 從BufferedReader中產生一個流: Collection.parallelStream()
二、詳解流的使用:
(1) map的詳解:map 生成的是個 1:1 對映,每個輸入元素,都按照規則轉換成為另外一個元素。還有一些場景,是一對多對映關係的,這時需要 flatMap。
例1:將陣列中的字串轉為大寫,求平方數
@Test
public void mapTest(){
//將陣列的字串轉為大寫
String[] wordList = new String[]{"Java","Android","OS","IOS"};
List<String> output = Arrays.stream(wordList).
map(String::toUpperCase).
collect(Collectors.toList());
System.out.println(output);
//平方數
List<Integer> nums = Arrays.asList(1,2,3,4);
List<Integer> squareNums = nums.stream().
map(n -> n * n).
collect(Collectors.toList());
System.out.println(squareNums);
}
注意:map(String::toUpperCase)這樣的寫法,這就是我們上篇部落格講的Lambda表示式的方法引用!誒,大家會不會覺得toUpperCase()這個方法不是String類中的靜態方法,為啥可以這樣呼叫呢?
map((str) -> str.toUpperCase()) 還原之前的Lambda表示式,toUpperCase()是String的成員方法,但是str會作為引數原封不動的傳遞給表示式str.toUpperCase(),這樣就可以使用String::toUpperCase改寫。
(2) filter 的詳解:filter對原始 Stream 進行某項測試,通過測試的元素被留下來生成一個新 Stream。
例2:過濾陣列中的奇數,留下偶數
@Test
public void filterTest(){
//留下偶數
Integer[] nums = {1,2,3,4,5,6};
Integer[] evens = Stream.of(nums).
filter(n -> n%2 == 0).
toArray(Integer[]::new);
for (Integer even : evens) {
System.out.println(even);
}
}
例3:將檔案中的單詞挑出,轉為小寫,排序並保證不重複
@Test
public void testDistinct() {
try {
//
BufferedReader br = new BufferedReader(new FileReader("src/main/resource/test.txt"));
//獲取不重複的單詞集合
List<String> wordList = br.lines().
flatMap(line -> Stream.of(line.split(" "))).
filter(word -> word.length() > 0).
map(String::toLowerCase).
distinct().
sorted().
collect(Collectors.toList());
System.out.println(wordList);
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
(3) reduce的詳解:這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然後依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字串拼接、數值的 sum、min、max、average 都是特殊的 reduce。
例4:字串拼接,求最小值、最大值、過濾字串拼接
@Test
public void reduce(){
//字串連線
String concat1 = Stream.of("A","B","C","D").reduce("", (x,y) -> x.concat(y));
String concat2= Stream.of("A","B","C","D").reduce("", String::concat);
System.out.println(concat1 + concat2);
//求最小值
double minValue1 = Stream.of(-1.5,1.0,-3.0,-2.0).reduce(Double.MAX_VALUE, (x,y) -> Double.min(x, y));
double minValue2 = Stream.of(-1.5,1.0,-3.0,-2.0).reduce(Double.MAX_VALUE, Double::min);
System.out.println(minValue1);
System.out.println(minValue2);
//求最和
int sumValue1 = Stream.of(1,2,3,4).reduce(0, (x,y) -> x + y);
//原因:Integer類中的sum()方法完全可以替代兩個數求和,引數型別、個數、返回值相同
int sumValue2 = Stream.of(1,2,3,4).reduce(0, Integer::sum);
System.out.println(sumValue1);
System.out.println(sumValue2);
//過濾字串並拼接
String concat = Stream.of("a","D","c","B","C","A").
filter(s -> s.compareTo("Z") > 0).
reduce("", String::concat);
System.out.println(concat);
}
(4) limit/skip的詳解:limit 返回 Stream 的前面 n 個元素;skip 則是扔掉前 n 個元素。
例5:留下陣列中的偶數,並扔掉前4 個元素。
@Test
public void testLimitAndSkip() {
List<Integer> list = new ArrayList();
for (int i = 1; i <= 20; i++) {
list.add(i);
}
//limit()返回前n個元素,skip則是扔掉前n個元素
List<Integer> list1 = list.stream().
filter(x -> x % 2 == 0).limit(10).skip(3).collect(Collectors.toList());
System.out.println(list1);
}
(5) sorted的詳解:對 Stream 的排序通過 sorted 進行,它比陣列的排序更強之處在於你可以首先對 Stream 進行各類 map、filter、limit、skip 甚至 distinct 來減少元素數量後,再排序,這能幫助程式明顯縮短執行時間。
@Test
public void testLimitAndSkip1() {
Integer[] arr = new Integer[]{2,5,3,4,7,8,5,12,13,16,18,19,20};
List<Integer> list = Arrays.asList(arr);
//limit()返回前n個元素,skip則是扔掉前n個元素
List<Integer> list1 = list.stream().
sorted(). //先排序,從I小到大輸出
filter(x -> x % 2 == 0).
limit(10).
skip(3).
collect(Collectors.toList());
System.out.println(list1);
}
(6) max/min/distinct的詳解:保證Stream流集合中元素不重複,找出最大值、最小值等
@Test
public void testMax() {
Integer[] arr = new Integer[]{2,5,3,4,7,8,5,12,13,16,18,19,20};
List<Integer> list = Arrays.asList(arr);
//求最大值
Integer maxValue = list.stream().
max(Integer::compare).get();
System.out.println(maxValue);
//求最小值
Integer minValue = list.stream().
min(Integer::compare).get();
System.out.println(minValue);
}
還有forEach(),peek(),allMatch(),anyMatch(),noMatch()方法,自行研究和舉例,這裡不在一一贅述!
三、Stream總結:
(1) Stream不是資料結構;
(2) 它沒有內部儲存,它只是用操作管道從 source(資料結構、陣列、generator function、IO channel)抓取資料;
(3) 它也絕不修改自己所封裝的底層資料結構的資料。例如 Stream 的 filter 操作會產生一個不包含被過濾元素的新 Stream,而不 是從 source 刪除那些元素;
(4) 所有 Stream 的操作必須以 lambda 表示式為引數;
(5) 不支援索引訪問;使用forEach()遍歷陣列,不可使用break,continue等關鍵字跳出迴圈;
(6) 你可以請求第一個元素findFirst(),但無法請求第二個,第三個,或最後一個。不過請參閱下一項。
(7) 很容易生成陣列或者 List。
簡單的講解了一下Java8新特性之Stream語法,希望大家看過後有所收穫,共同進步......
歡迎續學:Java8新特性之Stream學習二