Jdk8中Stream的簡介

前言

在jdk8環境中, 演示Stream的基本操作; 讀者需要了解Lambda表達式的用法, 作爲閱讀本文的預備知識.

演示的github鏈接

本文內容基本借鑑於 慕課網, 感謝!

Stream 簡介

java8中, 流的操作基本分三部分:

  • 構建流: 流的來源可能是集合, 數組, 文件, 其他來源暫不討論
  • 中間操作: 可以從原始的流開始, 執行多次, 每次執行返回一個新的流
  • 終端操作: 只能在流的最後, 執行一次的操作

注意以下幾點:

  • 流不存儲或改變其元素, 只返回新流或執行流中定義的操作
  • 流操作是惰性的, 只有終端操作被執行時, 其中間操作才執行
  • 所有流操作以函數式接口(lambda表達式) 作爲參數

構建流

1.由數值或對象直接構建流:

	@Test
    public void streamFromValue() {
        Stream stream1 = Stream.of(1, 2, 3, 4, 5);
        Stream stream2 =Stream.of(90, -2L, 2d,"22",new Integer(8), new Date());
//        Stream<Integer> stream3 =Stream.of(90, -2L, 2d,"22",new Integer(8), new Date());
        stream1.forEach(System.out::println);
        System.out.println("-------------");
        stream2.forEach(System.out::println);
    }

結果:

1
2
3
4
5
-------------
90
-2
2.0
22
8
Sat Nov 09 19:04:09 CST 2019

注意到, 可以給流添加泛型, 而統一流元素的類型; 不加泛型時, 可傳入任何類型

2.由數組構建流:

	@Test
    public void streamFromArray() {
        int[] numbers = {1, 2, 3, 4, 5};
        IntStream stream = Arrays.stream(numbers);
        stream.forEach(System.out::println);
    }

觀察Arrays.stream方法的源碼:

	public static <T> Stream<T> stream(T[] array) {
        return stream(array, 0, array.length);
    }

可以看到, 數組元素的類型和它所生成流的泛型保持一致;

如果數組是基本類型的, 所生成流的泛型是相應的包裝類

3.通過文件生成流

	@Test
    public void streamFromFile() throws IOException {
        // TODO 此處替換爲本地文件的地址全路徑
        String filePath = "";
        Stream<String> stream = Files.lines(
                Paths.get(filePath));
        stream.forEach(System.out::println);
    }

這個不多說了, 試下就知道

4.通過集合生成流(常見)
jdk8的Collection類中, 有一個default 的stream方法:

	default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

可以看到, 任何集合通過stream() 方法, 即可獲得擁有相同泛型的流; 這個用法後面演示會涉及, 就不贅述了

準備一個用於測試的集合

再貼一下 演示的github鏈接, 在sku包裏都有.

看一下這個集合的全貌:

@Test
    public void all(){
        list.stream()
                .forEach(System.out::println);
    }

結果是9個購物車中的商品:
在這裏插入圖片描述

有狀態操作(串行) 和無狀態操作(並行)的區別

有狀態操作, 就是需要收集流中所有元素後, 才能正確完成的操作(官方說法是: 需要內部狀態來累計計算結果); 無狀態操作, 就是除了有狀態操作的其他中間操作, 也可以理解成, 只依賴當前元素, 就能正確完成的操作.
在這裏插入圖片描述
因此, distinct()去重, sorted() 排序, limit(int i) 截斷只留前 i個, skip(int i) 跳過前 i個, 都是不可能依賴單個元素能正確完成的; 餘下的中間流都屬於無狀態的

可能有人對filter() 有疑問, 其實 filter() 是逐個判斷流元素是否符合斷言, 從而達到篩選效果, 可以理解成"篩出來一個, 就放行一個".

演示下有狀態操作 sort(), 這裏的演示中, 讀者不必在意流參數組織的形式, 而是注意有狀態操作和無狀態操作的區別:

@Test
    public void sortedAll() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuId()))
                .sorted(Comparator.comparing(sku -> sku.getSkuId()))
                .forEach(sku -> System.out.println("__" + sku.getSkuId() + "__"));
    }

在這裏插入圖片描述
可以看到, SkuId在執行了sort()前先被peek()輸出, 然後執行sort()後, 正確地從小到大輸出

演示下無狀態操作, 注意和上面的有狀態操作的對比:

@Test
    public void filterTest() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                .filter(sku -> null != sku)
                .forEach(sku -> System.out.println("__" + sku.getSkuName() + "__"));

    }

在這裏插入圖片描述
顯然, 這次不是整個流都執行完peek(), 再執行filter()了. 這裏peek()和 filter()都是無狀態流, 是並行流. 它們執行完一個元素就放行一個; 相反, 串行流執行完整個流, 再放行整個流

常用無狀態操作(並行操作)演示

filter(Predicate<? super T> predicate)

打開看Stream接口源碼中的 filter, 可以看到filter需要參數爲斷言型接口, 返回值仍是Stream類型的

@Test
    public void filterTest2() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuId()))
                .filter(sku -> sku.getSkuCategory().equals(SkuCategoryEnum.BOOKS))
                .forEach(System.out::println);
    }

在這裏插入圖片描述
從演示結果中, 看到filter操作是並行操作, 且將符合斷言型接口的元素, 放置到返回的流中

map(Function<? super T, ? extends R> mapper)

map的參數是一個函數式接口, 其功能是, 將一個元素改變後, 放入作爲返回值的流; 因此map具有轉換流的作用

@Test
    public void mapTest() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))		//簡單輸出下元素, 但經過此操作, 流不會改變
                .map(sku -> "__" + sku.getSkuName() + "__")
                .forEach(System.out::println);
    }

在這裏插入圖片描述
可以看到, map(Function<? super T, ? extends R> mapper) 依然是並行操作

flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

flatMap也是轉換流元素的中間操作, 但是它可以把一個元素轉換成多個元素的組成的流; 傳入參數依然是函數式接口

@Test
    public void flatMapTest() {
        list.stream()
                .peek(sku -> System.out.println())      //空輸出, 只用作換行
                .peek(sku -> System.out.println(sku.getSkuName()))
                .flatMap(sku -> Arrays.stream(sku.getSkuName().split("")))
                .forEach(ch -> System.out.print(ch +"\t"));
    }

在這裏插入圖片描述
可以看到, flatMap是並行操作, 它把接收到的單個元素轉換成流返回

peek(Consumer<? super T> action)

peek的功能經過前幾個例子, 大家應該很熟悉了, 其特點是針對流進行指定操作, 操作後流不會被改變.

但是這樣說並不確切, 因爲peek是典型的無狀態操作, 每次只針對一個元素. 實際上對其他無狀態操作, 如 filter, map, 都是改變當前操作元素, 並放到作爲返回值的流中, 以至於看上去整個流被改變了; peek是直接把當前元素放到作爲返回值的流中, 所以看上去流沒有改變.

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

有一類特殊的中間操作, 可以把輸入元素轉換成指定類型元素. 相當於指定返回元素
類型的map操作

@Test
    public void mapTest1(){
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuId()))
//                .mapToDouble(sku -> sku.getSkuName())   //轉換成錯誤的類型, 編譯報錯
                .mapToDouble(sku -> sku.getSkuId())
                .forEach(System.out::println);
    }

在這裏插入圖片描述

常用有狀態操作(串行操作)演示

sorted(Comparator<? super T> comparator) 和 sorted()

@Test
    public void sortTest() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                .sorted(Comparator.comparing(Sku::getSkuId).reversed())
                .forEach(System.out::println);
    }

在這裏插入圖片描述
看結果可知, sorted(Comparator<? super T> comparator) 方法根據傳入的Comparator接口, 對流元素進行排序, 使用 reversed() 可以指定逆序排序

再看 sorted() 操作:

@Test
    public void sortTest1(){
        list.stream()
                .map(sku -> sku.getSkuPrice())
                .sorted()	// 等同於 sorted((c1, c2) -> c1.compareTo(c2))
                .forEach(System.out::println);
    }

注意sorted() 接收的流元素所在的類, 必須實現Comparable 接口, 否則運行時, 無法知道比較的依據:

@Test
    public void sortTest2(){
        list.stream()
                .sorted()    // 等同於 sorted((c1, c2) -> c1.compareTo(c2))
                .forEach(System.out::println);
    }

在這裏插入圖片描述

distinct() 去重操作

@Test
    public void distinctTest() {
        list.stream()
                .map(sku -> sku.getSkuCategory())
                .distinct()
                .forEach(System.out::println);
    }

在這裏插入圖片描述

skip(long n), 對整個流跳過指定的前幾個元素, 再返回

@Test
    public void skipTest() {
        list.stream()
                .sorted(Comparator.comparing(sku -> sku.getTotalPrice()))
                .skip(3)
                .forEach(System.out::println);
    }

按總價排序, 再跳過總價最低的前三個
在這裏插入圖片描述

limit(long maxSize), 截取整個流的前若干個元素, 返回

@Test
    public void limitTest() {
        list.stream()
                .sorted(Comparator.comparing(Sku::getTotalPrice))
                .skip(2 * 3)
                // limit
                .limit(3)
                .forEach(System.out::println);
    }

在這裏插入圖片描述
可以看到, skip(long n)結合 limit(long n)使用, 可以模擬分頁效果

常用終端操作演示

boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)

@Test
    public void allMatchTest() {
        boolean match = list.stream()
                .peek(System.out::println)
                .allMatch(sku -> sku.getTotalPrice() > 100);
        System.out.println(match);
    }

在這裏插入圖片描述
可以看到, allMatch(Predicate<? super T> predicate) 根據斷言型接口篩選元素, 都匹配爲true, 如果遇到不匹配的第一個元素, 立即短路(不再選取下一個元素), 並返回false

anyMatch(Predicate<? super T> predicate), 任何一個匹配就短路, 返回true; noneMatch(Predicate<? super T> predicate), 任何一個匹配就短路, 返回 false, 直接把測試代碼貼在下面:

@Test
    public void anyMatchTest() {
        boolean match = list.stream()
                .peek(System.out::println)
                .anyMatch(sku -> sku.getTotalPrice() > 100);
        System.out.println(match);
    }

@Test
    public void noneMatchTest() {
        boolean match = list.stream()
                .peek(System.out::println)
                .noneMatch(sku -> sku.getTotalPrice() > 3000);
        System.out.println(match);
    }

collect() 方法演示

collect() 方法可以把流變成集合返回, 也可以把流按條件分組, 變成Map返回, 下面是兩個示例, 瞭解就可以

@Test
    public void toList() {
        List<Sku> list = CartService.getCartSkuList();
        List<Sku> result = list.stream()
                .filter(sku -> sku.getTotalPrice() > 100)
                .collect(Collectors.toList());
        result.stream()
                .forEach(System.out::println);
    }

在這裏插入圖片描述

@Test
    public void group() {
        List<Sku> list = CartService.getCartSkuList();
        Map<Object, List<Sku>> group = list.stream()
                .collect(Collectors.groupingBy(
                                sku -> sku.getSkuCategory()));  //以類別爲條件, 爲list分組
        for(Map.Entry entry: group.entrySet()){
            System.out.println(entry.getKey());
            ((List)entry.getValue()).stream()
                    .forEach(System.out::println);
        }
    }

在這裏插入圖片描述

發佈了18 篇原創文章 · 獲贊 3 · 訪問量 1052
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章