1. 程式人生 > 實用技巧 >【Java學習筆記十四】——走入Stream API

【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教學視訊