【Java學習筆記十四】——走入Stream API
宣告:本文章內容主要摘選自尚矽谷宋紅康Java教程、廖雪峰Java教程,示例程式碼部分出自本人,更多詳細內容推薦直接觀看以上教程及書籍,若有錯誤之處請指出,歡迎交流。
一、概念
1.Stream(流)是一個來自資料來源的元素佇列並支援聚合操作
- 元素是特定型別的物件,形成一個佇列。 Java中的Stream並不會儲存元素,而是按需計算。
- 資料來源 流的來源。 可以是集合,陣列,I/O channel, 產生器generator 等。
- 聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作還有兩個基礎的特徵:
- Pipelining: 中間操作都會返回流物件本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
- 內部迭代: 以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫做外部迭代。 Stream提供了內部迭代的方式, 通過訪問者模式(Visitor)實現。
2.應用場景
在需要用到某些操作時(求和、去重、按照要求過濾等),直接使用已經封裝好的Stream API進行實現,不需要我們自己寫程式碼實現,Stream API可以極大提高生產力,讓我們寫出高效率、乾淨、簡潔的程式碼。
二、建立Stream
- Stream 自己不會儲存元素。
- Stream 不會改變源物件,相反,他們會返回一個持有結果的新Stream。
- Stream 操作是延遲執行的。這意味者他們會等到需要結果的時保才執行。
1.通過Stream的of()
//直接用Stream.of()靜態方法,傳入可變引數即建立了一個能輸出確定元素的Stream:
Stream<Integer> stream = Stream.of(2,4,6,8);
//Stream 提供了新的方法 'forEach' 來迭代流中的每個資料。
stream.forEach(System.out::println);
2.通過陣列或集合
//基於一個數組或者集合建立,這樣該Stream輸出的元素就是陣列或者集合持有的元素:
//呼叫Arrays類的 Stream<T> stream(T[] array) :返回一個流
int[] arr1 = {1,2,3,4,5};
IntStream stream1 = Arrays.stream(arr1);
//Stream<E> stream() : 返回一個順序流
List<String> list = Arrays.asList("AA", "BB", "CC", "DD");
Stream<String> stream2 = list.stream();//通過集合建立
//Stream<E> parallelStream() :返回一個並行流
Stream<String> stream3 = list.parallelStream();
3.通過Supplier(無限流)
建立Stream
還可以通過Stream.generate()
方法,它需要傳入一個Supplier
物件:
基於Supplier
建立的Stream
會不斷呼叫Supplier.get()
方法來不斷產生下一個元素,這種Stream
儲存的不是元素,而是演算法,它可以用來表示無限序列。
import java.util.stream.Stream;
import java.util.function.*;
public class StreamTest1{
public static void main(String[] args){
//生成自然數
Stream<Integer> natual = Stream. generate(new NatualSupplier());
//注意:無限序列必須先變成有限序列再列印:
natual.limit(20).forEach(System.out::println);
}
//迭代:遍歷前10個偶數(使用Lambda表示式)
Stream.iterate(0, t ->t + 2).limit(10).forEach(System.out::println);
//生成:生成隨機數
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
class NatualSupplier implements Supplier<Integer> {
int n = 0;
public Integer get(){
n++;
return n;
}
}
上述程式碼我們用一個Supplier<Integer>
模擬了一個無限序列(當然受int
範圍限制不是真的無限大)。如果用List
表示,即便在int
範圍內,也會佔用巨大的記憶體,而Stream
幾乎不佔用空間,因為每個元素都是實時計算出來的,用的時候再算。
對於無限序列,如果直接呼叫forEach()
或者count()
這些最終求值操作,會進入死迴圈,因為永遠無法計算完這個序列,所以正確的方法是先把無限序列變成有限序列,例如,用limit()
方法可以擷取前面若干個元素,這樣就變成了一個有限序列,對這個有限序列呼叫forEach()
或者count()
操作就沒有問題。
三、執行中間操作
1.篩選與切片
public class StreamTest2{
public static void main(String[] args){
List<Employee> employees = EmployeeData.getEmployees();//Employee是我們自己定義的員工類,EmployeeData為資料來源,詳細請看帖子最底部的程式碼
//filter(Predicate p) : 接收Lambda, 通過設定的條件從流中過濾出元素。
Stream<Employee> stream1 = employees.stream();
//查詢員工表中年齡大於30的員工資訊
System.out.println("給定條件篩選元素:");
stream1.filter(e -> e.getAge() > 30).forEach(System.out::println);
//limit(n) : 截斷流,使其元素不超過給定數量
System.out.println("截斷指定數量的元素");
employees.stream().limit(3).forEach(System.out::println);
//skip(n) :跳過元素,返回一個扔掉了前n個元素的流,若流中元素不足n個,則返回一個空流,與limit(n)互補
System.out.println("跳過元素:");
employees.stream().skip(3).forEach(System.out::println);
//distinct() :篩選,通過流新生成元素的hashCode()和equals()去除重複元素
System.out.println("去重:");
Stream<String> list2 = Arrays.asList("a","a","b","c","c","d","a").stream();
list2.distinct().forEach(System.out::println);
}
2.對映
Stream.map()
是Stream
最常用的一個轉換方法,它把一個Stream
轉換為另一個Stream
。
所謂map
操作,就是接收一個函式作為引數,將元素轉換為其他形式或提取資訊,該函式會被應用到每一個元素上,並將其對映成一個流。例如,對x
計算它的平方,可以使用函式f(x) = x * x
。我們把這個函式對映到一個序列1,2,3,4,5上,就得到了另一個序列1,4,9,16,25:
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s.map(n -> n * n);
flatMap(Function f)
: 接收一個函式作為引數,將流中的每個值都換成另外一個流,然後把所有流連線成一個流
public class StreamTest2{
public static void main(String[] args){
//將一個流嵌入另外一個流,即作為另外一個流的一個元素
Stream<Stream<Character>> streamStream = list.stream().map(StreamTest2::fromStringToStream);
streamStream.forEach(s ->{
s.forEach(System.out::println); //2層遍歷方可遍歷到作為元素的流的元素(即”AA","BB"等)
});
//使用flatMap(Function f)
Stream<Character> characterStream = list.stream().flatMap(StreamTest2::fromStringToStream);
characterStream.forEach(System.out::println);//直接輸出集合中的集合元素的元素,無須再進行2層遍歷
//將字串中的多個字元構成的集合轉化為相應的Stream的例項
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list = new ArrayList<>();
for(Character c:str.toCharArray()){
list.add(c);
}
return list.stream();
}
}
}
3.排序
sorted ()
用於對流進行排序
//sorted():自然排序
System.out.println("自然排序:");
List<Integer> list = Arrays.asList(12,99,34,28,55,33);
list.stream().sorted().forEach(System.out::println);
//定製排序
System.out.println("按照員工年齡排序:");
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted((e1, e2)->Integer.compare(e1.getAge(), e2.getAge()))
.forEach(System.out::println);
//上方程式碼示例中的員工類和員工表
import java.util.ArrayList;
import java.util.List;
class EmployeeData {
public static List<Employee> getEmployees(){
List<Employee> list = new ArrayList<>();
list.add(new Employee(1001,"麻花騰",29));
list.add(new Employee(1002,"趙飛",32));
list.add(new Employee(1003,"林一",40));
list.add(new Employee(1004,"雷布斯",26));
list.add(new Employee(1005,"徐傑",43));
list.add(new Employee(1006,"秦飛",24));
list.add(new Employee(1007,"張三",33));
list.add(new Employee(1008,"何穎琳",24));
return list;
}
}
class Employee {
private int id;
private String name;
private int age;
public Employee(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
此筆記僅針對有一定程式設計基礎的同學,且本人只記錄比較重要的知識點,若想要入門Java可以先行觀看相關教程或書籍後再閱讀此筆記。
最後附一下相關連結:
Java線上API中文手冊
Java platform se8下載
尚矽谷Java教學視訊