1. 程式人生 > >Java之Stream流

Java之Stream流

map lock 調用 code rpe 方法 回流 增強 ons

Stream流的初步學習

初次學習Stream流的學習筆記,學習之前先了解一下函數式接口

概述

API是一個程序向使用者提供的一些方法,通過這些方法就能實現某些功能.所以對於流API來
說,重點是怎麽理解"流"這個概念,所謂的流:就是數據的渠道,所以,流代表的是一個對象的
序列.它和Java I/O類裏使用的"流"不同.雖然在概念上與java.util.stream中定義的流是類
似的,但它們是不同的.流API中的流是描述某個流類型的對象.

流API中的流操作的數據源,是數組或者是集合.它本身是不存儲數據的,只是移動數據,在移動
過程中可能會對數據進行過濾,排序或者其它操作.但是,一般情況下(絕大數情況下),流操作
本身不會修改數據源.比如,對流排序不會修改數據源的順序.相反,它會創建一個新的流,其
中包含排序後的結果.

“Stream流”其實是一個集合元素的函數模型,它並不是集合,也不是數據結構,其本身並不存儲任何
元素(或其地址值)。

Stream(流)是一個來自數據源的元素隊列

  • 元素是特定類型的對象,形成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算。
  • 數據源 流的來源。 可以是集合,數組 等。

和以前的Collection操作不同, Stream操作還有兩個基礎的特征:

  • Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent
    style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
  • 內部叠代: 以前對集合遍歷都是通過Iterator或者增強for的方式, 顯式的在集合外部進行叠代, 這叫做外部叠
    代。 Stream提供了內部叠代的方式,流可以直接調用遍歷方法。

當使用一個流的時候,通常包括三個基本步驟:獲取一個數據源(source)→ 數據轉換→執行操作獲取想要的結
果,每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以
像鏈條一樣排列,變成一個管道。

獲取流

java.util.stream.Stream

獲取一個流非常簡單,有以下幾種常用的方式:

  • 所有的Collection 集合都可以通過stream 默認方法獲取流;
    • default Stream
  • Stream 接口的靜態方法of 可以獲取數組對應的流。
    • static

      可以看出,Stream流是與容器相關的.

下面例子演示如何獲取Stream流:

package com.wzlove.stream.demo2;

import java.util.*;
import java.util.stream.Stream;

/**
 * @創建人 王智
 * @創建時間 2018/7/28
 * @描述 演示獲取Stream流
 */
public class ConvertStream {

    public static void main(String[] args) {
        // 單列集合根據Collection的stream方法獲取流
        // 數組集合
        ArrayList<String> arrayList = new ArrayList<>();
        Stream<String> stream1 = arrayList.stream();
        // set集合
        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();
        // Map集合不能直接轉化為Stream流,但是可以先將Map集合轉化為Set集合
        Map<String,String> map = new HashMap<>();
        // map集合轉化為Set集合再轉化為Stream流
        Stream<String> stream3 = map.keySet().stream();
        Stream<String> stream4 = map.values().stream();
        Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();

        // 使用第二種方式獲取Stream流
        // 傳遞數組
        Stream<int[]> stream6 = Stream.of(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
        // 傳入可變參數
        Stream<Integer> stream7 = Stream.of(1, 2, 3, 4, 5, 6);
    }
}

Stream的常用方法

流模型的操作很豐富,這裏介紹一些常用的API。這些方法可以被分成兩種:

  • 延遲方法:返回值類型仍然是Stream 接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其余方
    法均為延遲方法。)
  • 終結方法:返回值類型不再是Stream 接口自身類型的方法,因此不再支持類似StringBuilder 那樣的鏈式調
    用。本小節中,終結方法包括count 和forEach 方法。

註意 : Stream流只能被使用一次,再次使用會拋出異常,說流已經關閉.

逐一處理

方法不一一演示,最後統一演示:

  • void forEach(Consumer<? super T> action);該方法接收一個Consumer 接口函數,會將每一個流元素交給該函數進行處理。

終結方法,遍歷之後就不能再調用Stream流中的其他方法

過濾

  • Stream

映射

統計個數

  • long count();該方法返回一個long值代表元素個數(不再像舊集合那樣是int值)

也是一個終結方法

取用前幾個

  • Stream

跳過前幾個

  • Stream

組合

  • static

備註:這是一個靜態方法,與java.lang.String 當中的concat 方法是不同的。

演示前幾個常用方法:

package com.wzlove.stream.demo2;

import java.util.ArrayList;

/**
 * @創建人 王智
 * @創建時間 2018/7/28
 * @描述 測試Stream流的常用方法
 */
public class StreamUsuallyMethod {

    public static void main(String[] args) {
        // 準備數據
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(5);
        arrayList.add(3);
        arrayList.add(9);
        arrayList.add(1);
        arrayList.add(4);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(6);
        arrayList.add(8);
        arrayList.add(7);
        // 測試Stream的forEach方法
        System.out.print("forEach方法的測試 : ");
        arrayList.stream().forEach(elem -> System.out.print(elem + "  "));
        System.out.println();

        // 測試Filter方法
        // 獲取集合中大於5的數字
        System.out.print("獲取集合中大於5的數字 : ");
        arrayList.stream().filter(elem -> elem > 5).forEach(elem -> System.out.print(elem + "   "));
        System.out.println();
        // 獲取集合中大於2小於7的數字
        System.out.print("獲取集合中大於2小於7的數字 : ");
        arrayList.stream().filter(elem -> elem > 2).filter(elem -> elem < 7).forEach(elem -> System.out.print(elem + "   "));
        System.out.println();


        // 測試Map方法
        // 將集合轉化為String集合
        System.out.print("map方法將int轉化為String : ");
        arrayList.stream().map(elem -> String.valueOf(elem)).forEach(elem -> System.out.print(elem + "   "));
        System.out.println();
    }
}

舉個例子,使用所有的常用方法:

package com.wzlove.stream.demo2;

import java.util.ArrayList;
import java.util.stream.Stream;

/**
 * @author 王智
 * @date 2018/7/28
 * @描述
 *  1. 第一個隊伍只要名字為3個字的成員姓名;存儲到一個新集合中。
 * 2. 第一個隊伍篩選之後只要前3個人;存儲到一個新集合中。
 * 3. 第二個隊伍只要姓張的成員姓名;存儲到一個新集合中。
 * 4. 第二個隊伍篩選之後不要前2個人;存儲到一個新集合中。
 * 5. 將兩個隊伍合並為一個隊伍;存儲到一個新集合中。
 * 6. 根據姓名創建Person 對象;存儲到一個新集合中。
 * 7. 打印整個隊伍的Person對象信息。
 */
public class StreamDemo {

    public static void main(String[] args) {

        //第一支隊伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪麗熱巴");
        one.add("宋遠橋");
        one.add("蘇星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("莊子");
        one.add("洪七公");
        //第二支隊伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜紮");
        two.add("張無忌");
        two.add("趙麗穎");
        two.add("張三豐");
        two.add("尼古拉斯趙四");
        two.add("張天愛");
        two.add("張二狗");
        /*
         *  1. 第一個隊伍只要名字為3個字的成員姓名;存儲到一個新集合中。
         * 2. 第一個隊伍篩選之後只要前3個人;存儲到一個新集合中。
         * 3. 第二個隊伍只要姓張的成員姓名;存儲到一個新集合中。
         * 4. 第二個隊伍篩選之後不要前2個人;存儲到一個新集合中。
         * 5. 將兩個隊伍合並為一個隊伍;存儲到一個新集合中。
         * 6. 根據姓名創建Person 對象;存儲到一個新集合中。
         * 7. 打印整個隊伍的Person對象信息。
         */
        // 直接使用Stream流來完成
        Stream.concat(
                one.stream().filter(elem -> elem.length() == 3).limit(3),
                two.stream().filter(elem -> elem.startsWith("張")).skip(2))
                .map(elem -> new Person(elem))
                .forEach(elem -> System.out.println(elem));
    }
}

方法引用

很少使用

:: 寫法就是方法的引用,它的出現是為了簡化Lambda的寫法.

System.out 對象中有一個重載的println(String) 方法恰好就是我們所需要的。那麽對於
printString 方法的函數式接口參數,對比下面兩種寫法,完全等效:

  • Lambda表達式寫法: s -> System.out.println(s);
  • 方法引用寫法: System.out::println

第一種語義是指:拿到參數之後經Lambda之手,繼而傳遞給System.out.println 方法去處理。

第二種等效寫法的語義是指:直接讓System.out 中的println 方法來取代Lambda。兩種寫法的執行效果完全一
樣,而第二種方法引用的寫法復用了已有方案,更加簡潔。

註:Lambda 中 傳遞的參數 一定是方法引用中 的那個方法可以接收的類型,否則會拋出異常

類型有
這些很少用,是要能看懂就行

  • 通過對象名引用成員方法(demo1)

      package com.wzlove.method.demo1;
      /*
          定義一個打印的函數式接口
       */
      @FunctionalInterface
      public interface Printable {
          //定義字符串的抽象方法
          void print(String s);
      }
    
      package com.wzlove.method.demo1;
    
      public class MethodRerObject {
          //定義一個成員方法,傳遞字符串,把字符串按照大寫輸出
          public void printUpperCaseString(String str){
              System.out.println(str.toUpperCase());
          }
      }
    
      package com.wzlove.method.demo1;
    
      /**
       * @author 王智
       * @date 2018/7/28
       * @描述 使用方法引用簡化Lambda表達式,對象調用方法
       */
      public class Demo1 {
    
          public static void printString(Printable printable){
              printable.print("java");
          }
    
          public static void main(String[] args) {
    
              // 簡化前的寫法
              printString(s->{
                  // 創建對象
                  MethodRerObject methodRerObject = new MethodRerObject();
                  // 調用方法
                  methodRerObject.printUpperCaseString(s);
              });
              // 簡化後的寫法,對象調用方法
              // 前提是對象存在,方法存在
              printString(new MethodRerObject()::printUpperCaseString);
          }
      }
  • 通過類名稱引用靜態方法(demo2)

    package com.wzlove.method.demo2;

    /**
    • @author 王智
    • @date 2018/7/28
    • @描述 計算絕對值的接口
      */
      @FunctionalInterface
      public interface Calcable {

      int intAbs(int num);
      }

    package com.wzlove.method.demo2;

    /**
    • @author 王智
    • @date 2018/7/28
    • @描述
      */
      public class Demo2 {

      public static int methodAds(int num, Calcable calcable){
      return calcable.intAbs(num);
      }

      public static void main(String[] args) {
      // Lambda的寫法
      int num1 = methodAds(-100,num->Math.abs(num));
      System.out.println(num1);
      // 簡化Lambda的寫法
      // 前提是類存在,靜態方法存在
      int result = methodAds(-100,Math::abs);
      System.out.println(result);
      }
      }

  • 通過super引用成員方法
  • 通過this引用成員方法
  • 類的構造器的使用(demo3)

    package com.wzlove.method.demo3;

    public class Person {
    private String name;

      public Person() {
      }
    
      public Person(String name) {
          this.name = name;
      }
    
      public String getName() {
          return name;
      }
    
      public void setName(String name) {
          this.name = name;
      }

    }

    package com.wzlove.method.demo3;
    /
    定義一個創建Person對象的函數式接口
    /
    @FunctionalInterface
    public interface PersonBuilder {
    //定義一個方法,根據傳遞的姓名,創建Person對象返回
    Person builderPerson(String name);
    }

    package com.wzlove.method.demo3;

    /**
    • @author 王智
    • @date 2018/7/28
    • @描述
      */
      public class Demo3 {

      public static void buildPerson(String name, PersonBuilder pb){

       Person person = pb.builderPerson(name);
       System.out.println(person.getName());

      }

      public static void main(String[] args) {

       buildPerson("楊紫",(name)->{
           return new Person(name);
       });
       // 方法引用
       // 有參構造
       buildPerson("迪麗熱巴",Person::new);

      }

    }

  • 數組的構造器的引用(demo4)

    package com.wzlove.method.demo4;
    /
    定義一個創建數組的函數式接口
    /
    @FunctionalInterface
    public interface ArrayBuilder {
    //定義一個創建int類型數組的方法,參數傳遞數組的長度,返回創建好的int類型數組
    int[] builderArray(int length);
    }

    package com.wzlove.method.demo4;

    /**
    • @author 王智
    • @date 2018/7/28
    • @描述
      */
      public class Demo4 {

      public static int[] builderArr(int length, ArrayBuilder ab){

       return ab.builderArray(length);

      }

      public static void main(String[] args) {
      int[] arr1 = builderArr(5,length->new int[length]);
      // 創建數組
      int[] arr = builderArr(5,int[] :: new );
      }
      }

Java之Stream流