Java8 Lambda表示式與Stream API (二): Stream API的使用
你要知道的Java8 匿名內部類、函式式介面、lambda表示式與Stream API都在這裡
轉載請註明出處 http://www.jianshu.com/p/3bdd8853016d
本文主要講解Java8 Stream API
,但是要講解這一部分需要匿名內部類、lambda表示式以及函式式介面的相關知識,本文將分為兩篇文章來講解上述內容,讀者可以按需查閱。
本文是本系列文章的第二篇,主要講解Stream API
,在學習Stream API
之前要求讀者有一定的lambda表示式
基礎,如果相關知識不瞭解可以參考本系列文章的第一篇Java 匿名內部類、lambda表示式與函式式介面。
Stream API
Java8
新增的stream
功能非常強大,這裡的stream
和Java IO
中的stream
是完全不同概念的兩個東西。本文要講解的stream
是能夠對集合物件進行各種序列或併發聚集操作,Stream API
依賴於前一篇文講解的lambda表示式
,只有當兩者結合時才能極大的提高程式設計效率並且程式碼更易理解和維護。Stream API
支援序列和併發的集合操作,這也是響應了現在多核處理器的需求,Stream API
的併發採用的是我們熟悉的fork/join
模式,手動編寫並行程式碼很複雜也很容易出錯,但是採用Stream API
來進行集合物件上的併發操作你不需要編寫任何多執行緒程式碼就能夠輕而易舉的實現併發操作,從而提高程式碼的執行效率,也極大的簡化了程式設計難度。
聚集操作
在實際開發中,我們經常對一個集合內的物件進行一系列的操作,比如排序、查詢、過濾、重組、資料統計等操作,通常情況下我們可能會採用for迴圈
遍歷的方式來逐一進行操作,這樣的程式碼即複雜又難以維護,如果對效能有要求再進行多執行緒程式碼的編寫就更加的複雜了,同時也更容易出錯。
下面舉一個栗子:
class User
{
private String userID;
private boolean isVip;
private int balance;
public User(String userID, boolean isVip, int balance)
{
this.userID = userID;
this.isVip = isVip;
this.balance = balance;
}
public boolean isVip()
{
return this.isVip;
}
public String getUserID()
{
return this.userID;
}
public int getBalance()
{
return this.balance;
}
}
public class HelloWorld
{
public static void main(String[] args)
{
ArrayList<User> users = new ArrayList<>();
users.add(new User("2017001", false, 0));
users.add(new User("2017002", true, 36));
users.add(new User("2017003", false, 98));
users.add(new User("2017004", false, 233));
users.add(new User("2017005", true, 68));
users.add(new User("2017006", true, 599));
users.add(new User("2017007", true, 1023));
users.add(new User("2017008", false, 9));
users.add(new User("2017009", false, 66));
users.add(new User("2017010", false, 88));
//普通實現方式
ArrayList<User> tempArray = new ArrayList<>();
ArrayList<String> idArray = new ArrayList<>(3);
for (User user: users)
{
if (user.isVip())
{
tempArray.add(user);
}
}
tempArray.sort(new Comparator<User>(){
public int compare(User o1, User o2) {
return o2.getBalance() - o1.getBalance();
}
});
for (int i = 0; i < 3; i++)
{
idArray.add(tempArray.get(i).getUserID());
}
for (int i = 0; i < idArray.size(); i++)
{
System.out.println(idArray.get(i));
}
//Stream API實現方式
//也可以使用parallelStream方法獲取一個併發的stream,提高計算效率
Stream<User> stream = users.stream();
List<String> array = stream.filter(User::isVip).sorted((t1, t2) -> t2.getBalance() - t1.getBalance()).limit(3).map(User::getUserID).collect(Collectors.toList());
array.forEach(System.out::println);
}
}
上述程式碼首先定義了一個使用者類,這個類儲存使用者是否是VIP、使用者ID以及使用者的餘額,假如現在有一個需求,將VIP中餘額最高的三個使用者的ID找出來,傳統的思路一般就是建立一個臨時的list
,然後逐一判斷,將所有的VIP使用者加入到這個臨時的list
中,然後呼叫集合類的sort
方法根據餘額排序,最後再遍歷三次獲取餘額最高的三個使用者的ID等資訊。這樣的方法看似簡單,但程式碼寫出來即混亂也不好看,如果使用者量非常大,有幾千萬甚至幾個億,這樣遍歷的方式效率就會特別低,如果手工加上多執行緒的併發操作,程式碼就更加複雜了。
上述程式碼的第二部分使用Stream API
的方式來計算,首先通過集合類獲取了一個普通的stream
,如果資料量大可以使用parallelStream
方法獲取一個併發的stream
,這樣接下來的計算程式設計師不需要編寫任何多執行緒程式碼系統會自動進行多執行緒計算。獲取了stream
以後首先呼叫filter
方法找到是否為VIP使用者然後對VIP使用者進行排序操作,接下來限制只獲取三個使用者的資訊,然後將使用者對映為使用者ID,最後將該stream
轉換為集合類,兩種實現方式的結果完全一樣,但是明顯的採用Stream API
的程式碼更加簡潔易懂。
Stream API
的編寫大量依賴lambda表示式
以及lambda表示式
的引用方法
和引用構造器
,如果您對這一塊不理解可以查閱文章Java 匿名內部類、lambda表示式與函式式介面。
如何使用Stream
A sequence of elements supporting sequential and parallel aggregate operations
上面是Java
文件中定義的Stream
,可以看出,Stream
就是元素的集合,並且可以採用序列或並行的方式進行聚集操作。在使用時我們可以將Stream
理解為一個迭代器,只不過這個迭代器更加高階,能夠對其中的每一個元素進行我們規定的計算。
當我們要使用Stream API
時,首先需要建立一個Stream
物件,可以通過集合類的例項方法stream
或parallelStream
來獲取一個普通的序列stream
或是並行stream
。也可以使用Stream
、IntStream
、LongStream
或DoubleStream
建立一個Stream
物件,Stream
是一個比較通用的流,可以代表任何引用資料型別,其他的則是指特定型別的流。最常用的就是通過一個集合型別來獲取相應型別的Stream
。
流的操作分為中間操作 Intermediate
和結束操作 Terminal
:
- 中間操作(Intermediate):一個流可以採用鏈式呼叫的方式進行數箇中間操作,主要目的就是開啟流然後對這個流進行各種過濾、對映、聚集、統計操作等,如上述程式碼中的
filter
、map
操作等。每一個操作結束後都會返回一個新的流,並且這些操作都是lazy的,也就是在進行結束操作時才會真正的進行計算,一次遍歷就計算出所有結果。 - 結束操作(Terminal):一個流只能執行一個結束操作,當執行了結束操作以後這個流就不能再被執行,也就是說不能再次進行中間操作或結束操作,所以結束操作一定是流的最後一個操作,如上述程式碼中的
collect
方法。當開始執行結束操作的時候才會對流進行遍歷並且只一次遍歷就計算出所有結果。
Stream的建立
- 通過集合類建立
通過集合建立Stream
的方法是我們最常用的,集合類的例項方法stream
和parallelStream
可以獲取相應的流。
ArrayList<User> users = new ArrayList<>();
users.add(new User("2017001", false, 0));
users.add(new User("2017002", true, 36));
users.add(new User("2017003", false, 98));
Stream<User> stream = users.stream();
- 通過陣列構造
String[] str = {"Hello World", "Jiaming Chen", "Zhouhang Cheng"};
Stream<String> stream = Stream.of(str);
- 通過單個元素構造
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
- Stream與Array和Collection的轉換
一般我們都會對Stream
進行結束操作,用於獲取一個陣列或是集合類,通過陣列和集合類建立Stream
前文已經介紹了,這裡介紹通過Stream
獲取陣列或集合類。
String[] str = {"Hello World", "Jiaming Chen", "Zhouhang Cheng"};
Stream<String> stream = Stream.of(str);
String[] strArray = stream.toArray(String[]::new);
List<String> strList = stream.collect(Collectors.toList());
ArrayList<String> strArrayList = stream.collect(Collectors.toCollection(ArrayList::new));
Set<String> strSet = stream.collect(Collectors.toSet());
上面的程式碼分別將流轉換為陣列、List、ArrayList和Set型別,具體的引數可以檢視官方API文件。
Stream 常用方法
- filter
filter
的栗子前面已經舉過了,filter
函式需要傳入一個實現Predicate
函式式介面的物件,該介面的抽象方法test
接收一個引數並返回一個boolean
值,為true
則保留,false
則剔除,前文舉的栗子就是判斷是否為VIP使用者,如果是就保留,不是就剔除。
原理如圖所示:
- map、flatMap
map
的栗子前面已經舉過了,map
函式需要傳入一個實現Function
函式式介面的物件,該介面的抽象方法apply
接收一個引數並返回一個值,可以理解為對映關係,前文舉的栗子就是將每一個使用者對映為一個userID
。
原理如圖所示:
map
方法是一個一對一的對映,每輸入一個資料也只會輸出一個值。flatMap
方法是一對多的對映,對每一個元素對映出來的仍舊是一個Stream
,然後會將這個子Stream
的元素對映到父集合中,栗子如下:
Stream<List<Integer>> inputStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6));
List<Integer> integerList = inputStream.flatMap((childList) -> childList.stream()).collect(Collectors.toList());
//將一個“二維陣列”flat為“一維陣列”
integerList.forEach(System.out::println);
- limit、skip
limit
用於限制獲取多少個結果,與資料庫中的limit
作用類似,skip
用於排除前多少個結果。
- sorted
sorted
的栗子前面也舉過了,sorted
函式需要傳入一個實現Comparator
函式式介面的物件,該介面的抽象方法compare
接收兩個引數並返回一個整型值,作用就是排序,與其他常見排序方法一致。
- distinct
distinct
用於剔除重複,與資料庫中的distinct
用法一致。
- findFirst
findFirst
方法總是返回第一個元素,如果沒有則返回空,它的返回值型別是Optional<T>
型別,接觸過swift
的同學應該知道,這是一個可選型別,如果有第一個元素則Optional
型別中儲存的有值,如果沒有第一個元素則該型別為空。
Stream<User> stream = users.stream();
Optional<String> userID = stream.filter(User::isVip).sorted((t1, t2) -> t2.getBalance() - t1.getBalance()).limit(3).map(User::getUserID).findFirst();
userID.ifPresent(uid -> System.out.println("Exists"));
- min、max
min
可以對整型流求最小值,返回OptionalInt
。max
可以對整型流求最大值,返回OptionalInt
。
這兩個方法是結束操作,只能呼叫一次。
- allMatch、anyMatch、noneMatch
allMatch
:Stream
中全部元素符合傳入的predicate
返回 true
anyMatch
:Stream
中只要有一個元素符合傳入的predicate
返回 true
noneMatch
:Stream
中沒有一個元素符合傳入的predicate
返回 true
- reduce
reduce
方法用於組合Stream
元素,它可以提供一個初始值然後按照傳入的計算規則依次和Stream
中的元素進行計算,因此上文介紹的min
、max
都可以看做是reduce
的一種實現。
舉個例子:
IntStream is = IntStream.range(0, 10);
System.out.println(is.reduce(0, Integer::sum));
IntStream intStream = IntStream.range(0, 10);
System.out.println(intStream.reduce((o1, o2) -> o1 + o2));
Stream<String> stream = Stream.of("Hello", "World", "Jiaming", "Chen");
System.out.println(stream.reduce("", String::concat));
第一個IntStream
呼叫的reduce
方法設定了一個初始值,因此最終reduce
計算的結果一定有值,該方法呼叫Integer
的類方法sum
用於計算Stream
的總和。
第二個IntStream
呼叫reduce
方法時沒有設定初始值,因此最終reduce
計算的結果不一定有值,所以返回值型別是Optional
型別,沒有提供初始值時會自動將第一個和第二個元素先進行計算,但有可能不存在第一個或第二個元素,因此返回值是Optional
型別。
Stream API的效能
這篇文章詳細測試了Stream API
的效能Java Stream API效能測試。
總的來說,對於複雜計算並且擁有多核CPU來說,使用Stream API
進行併發計算速度最快,也推薦使用。對於計算比較簡單,手工外部迭代效能更加。單核CPU儘量不要使用併發的Stream API
計算。如果沒有太高的效能要求,想要編寫出簡潔的程式碼還是推薦使用Stream API
。
備註
由於作者水平有限,難免出現紕漏,如有問題還請不吝賜教。
相關文章
- Java8中的 lambda 和Stream APIJavaAPI
- Java8的Stream API使用JavaAPI
- Java8中的Stream APIJavaAPI
- Java8新特性——從Lambda表示式到Stream流Java
- Java8新特性--Stream APIJavaAPI
- Java8 Stream常用API整理JavaAPI
- java8 Stream APi 入門JavaAPI
- Java8 - Stream API快速入門JavaAPI
- Stream APIAPI
- Java8 Lambda 之 Collection StreamJava
- Java8新特性第3章(Stream API)JavaAPI
- Java 8新特性(二):Stream APIJavaAPI
- Node.js 中 Stream API 的使用Node.jsAPI
- Java 8中的Stream API使用指南JavaAPI
- 13. 尚矽谷_Java8新特性_Stream API 練習JavaAPI
- Java Lambda StreamJava
- Java8的Lambda表示式Java
- java8 lambda表示式Java
- Java8中的Lambda表示式Java
- Java 8 Stream Api 中的 peek 操作JavaAPI
- java8特性-lambda表示式Java
- Java8特性詳解 lambda表示式(二):流式處理中的lambdaJava
- 7個Java Stream API面試題JavaAPI面試題
- JTCR-Stream API-23API
- Java8之Stream-函式式介面Java函式
- Java 8 Stream API 轉換到 Kotlin 集合APIJavaAPIKotlin
- 【Java8新特性】面試官:談談Java8中的Stream API有哪些終止操作?Java面試API
- 【Java8新特性】關於Java8的Stream API,看這一篇就夠了!!JavaAPI
- Java8 Stream完全使用指南Java
- Java8——Stream流Java
- Go語言實現的Java Stream APIGoJavaAPI
- Java8 Lambda 表示式有何用處?如何使用?Java
- 【譯】java8之lambda表示式Java
- Java 8新特性之旅:使用Stream API處理集合JavaAPI
- Java Stream API groupingBy()介紹JavaAPI
- Java 10中Stream API不可變集合JavaAPI
- Java8 Stream的總結Java
- list轉map,使用java8,stream流Java