Java8 Stream流編程

Java8 流式編程

流是一系列與特定存儲機制無關的元素——實際上,流並無「存儲」之說。php

使用流,無需迭代集合中的元素,就能夠提取/操做特定的元素java

假設我要生成一個隨機序列,範圍在5到100之間,不重複,隨機生成7個數字,並且要排序,最後輸出序列,能夠這麼作:python

public static void main(String[] args) {
    new Random(47)  // 種子
        .ints(5, 100)
        .distinct()
        .limit(7)
        .sorted()
        .forEach(System.out::println);
}

整個過程是一個工做流,它不須要單獨提取出這個序列在進行操做。c++

若是要實現剛剛的功能,而不使用流,就可能會是這樣子:正則表達式

public static void main(String[] args) {
    Random random = new Random(47);
    SortedSet<Integer> set = new TreeSet<>();
    while (set.size() < 7){
        int r = random.nextInt(100);
        if(r >= 5){
            set.add(r);
        }
    }
    System.out.println(set);
}

流操做的類型有三種:數據庫

  • 建立流
  • 修改流元素
  • 消費流元素(終端操做,打印/收集元素等等)
    • .forEach()
    • .sum()

建立流

能夠用Steam.of將對象數組/一組元素轉換爲 編程

String[] arr = {"C++ ", "Python ", "Java"};
Stream.of(arr).forEach(System.out::print);
Stream.of("C++ ", "Python ", "Java ").forEach(System.out::print);

每一個集合也能夠用.stream()來產生一個流數組

List<String> strList = Arrays.asList("C++ ", "Python ", "Java ");
strList.stream().forEach(System.out::print);

// C++ Python Java

對於原始數據類型的數組/列表,能夠用Arrays.stream()安全

  • 若是用Steam.of(),輸出的是一個地址
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Arrays.stream(arr).forEach(System.out::println);    // 輸出元素1 2 3 4 ...
Stream.of(arr).forEach(System.out::println);        // 輸出地址 [I@214c265e

建立隨機數流

生成4個隨機整數,輸出服務器

  • ints(n):控制流的大小爲4,生成4個整數,返回一個IntStream
  • forEach(System.out::println):打印每一個元素
Random random = new Random(47);	// 47是參數,種子
random.ints(4).forEach(System.out::println);

生成5個隨機整數,只取前3個整數

Random random = new Random();
random.ints(5).limit(3).forEach(System.out::println);

建立6個隨機整數,範圍在10~20之間

  • ints(m,n):生成[m,n)之間的整數,返回一個IntStream
  • 用了ints(m,n)的方法,要加limit(),否則就是無限循環
Random random = new Random();
random.ints(10,20).limit(6).forEach(System.out::println);

也能夠用init(streamSize,numberOrigin,numberBound)來建立:

  • 第一個參數是流的大小,第二第三個參數是表示隨機數的範圍
  • 下面這段代碼就是:生成6個隨機整數,範圍在10~20之間
Random random = new Random();
random.ints(6,10,20).forEach(System.out::println);

建立整形序列流

假設我要建立一個[1,100]的連續序列,求它的和,能夠用 IntStream 提供的 range(m,n)方法

public static void main(String[] args) {
    // 1-100的序列數組
    int[] intsArr = IntStream.range(1, 101).toArray();
    int sum = Arrays.stream(intsArr).sum();
    System.out.println(sum);
}

能夠寫的再簡單一點:

public static void main(String[] args) {
    // 1-100的序列數組
    int sum = IntStream.range(1, 101).sum();
    System.out.println(sum);
}

對比傳統的方法,就會以爲用 會方便不少:

// 傳統方法
public static void main(String[] args) {
    int sum = 0;
    for(int i = 1; i <= 100;++i){
        sum += i;
    }
    System.out.println(sum);
}

使用Stream.generate()建立

Stream.generate() 返回無限流,咱們能夠自定義生成的元素

生成5個隨機整數

Stream.generate(() -> {
    return new Random().nextInt();
}).limit(5).forEach(System.out::println);

也能夠簡寫成:

Stream.generate(new Random()::nextInt).limit(5).forEach(System.out::println);

使用Stream.iterate()建立

方法原型:

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
	// ...   
}

第一個參數:種子

第二個參數:方法(lambda表達式)

將 種子 傳遞給 方法,方法的運行結果 做爲流的下一個元素,添加到流中,存儲起來,做爲下次調用iterate()的第二個參數

iterate()生成斐波那契數列

iterate()只記憶結果

public class Generator {
    private int x = 0;
    Stream<Integer> numbers(){
        return Stream.iterate(1,(i)->{
            int res = x + i;
            x = i;
            return res;
        });
    }

    public static void main(String[] args) {
        // 生成前10項
        new Generator().numbers().limit(10).forEach(System.out::println);
        
        // 生成第10到20項
        new Generator()
            .numbers()
            .skip(10)	// 過濾前10個
            .limit(10)	// 而後取10個
            .forEach(System.out::println);
    }
}

Arrays.stream()Stream.of()

Arrays 類中含有一個名爲 stream() 的靜態方法用於把 int/long/double數組 轉換成爲流。

int、double、long等基本類型的數組,用 Arrays.stream(數組名) 進行流的操做

若是是String(String是一個對象),能夠用 Stream.of(對象數組名) 進行流的操做

Stream.of()Arrays.stream() 是有區別的:

  • 若是是基本類型的對象數組(如IntegerLong),兩個方法是同樣的結果
Integer[] intArr = {1, 2, 3, 4};
Arrays.stream(intArr).forEach(System.out::print);	// 1234
Stream.of(intArr).forEach(System.out::print);		// 1234
  • 若是是基本類型的數組(如intlong),兩個方法返回的結果不同
int[] intArr = {1, 2, 3, 4};
Arrays.stream(intArr).forEach(System.out::print);	// 1234
Stream.of(intArr).forEach(System.out::print);		// [I@214c265e

這是由於int[]傳入Stream.of()會被看成一個對象,而傳入Arrays.stream()會被當成一個數組

流的中間操做

什麼是流的中間操做?就是修改流中的元素,返回一個修改後的Stream。

一個流建立以後有6個元素,我跳過前3個,只取最後3個,那中間操做就是 「跳過前三個」

peek() 跟蹤和調試

peek() 的目的是:無修改的查看流中的元素

peek()參數可接收lambda表達式

當流中的一個元素經過管道的時候,就會調用一次peek()

例1

public static void main(String[] args) {
    String[] str = {"java ", "c++  ", "php  ", "dart "};
    Stream.of(str)
        .skip(1)
        .peek(System.out::print)	// 輸出當前流的狀況
        .map(s -> "第1次變化:" + s)	// 映射,修改str中字符串的值
        .peek(System.out::print)
        .forEach(System.out::println);
}

例1-輸出結果:

c++  第1次變化:c++  第1次變化:c++  
php  第1次變化:php  第1次變化:php  
dart 第1次變化:dart 第1次變化:dart

例2

public static void main(String[] args) {
    String[] str = {"java ", "c++  ", "php  ", "dart "};
    Stream.of(str)
        .skip(1)
        .peek(System.out::print)
        .map(String::toUpperCase)	// 映射,將流中的元素全轉成大寫
        .forEach(System.out::print);
}

例2-輸出結果

c++  C++  php  PHP  dart DART

sorted() 排序

sorted()接收一個lambda表達式,也能夠接收一個 比較器sorted()預設了一些默認的比較器)

基本類型數組排序

我查了資料,發現 基本類型的數組的sorted()不能傳比較器,它們的包裝類就能夠

public static void main(String[] args) {
    // 基本類型數組
    int[] arr = {1, 18, 12, 16, 4};
    // 默認正序
    Arrays.stream(arr).sorted().forEach(System.out::println);	// 1 4 12 16 18
}

對象/包裝類數組排序

public static void main(String[] args) {
    // Integer類型數組
    Integer[] integerArr = {1, 18, 12, 16, 4};
    // 正序
    Arrays.stream(integerArr).sorted().forEach(System.out::println);	// 1 4 12 16 18
    // 逆序
    Arrays.stream(arr).sorted(Comparator.reverseOrder()).forEach(System.out::println);	// 18 16 12 4 1
}

集合排序

public static void main(String[] args) {   
    // List 集合
    List<Integer> list = new LinkedList<>(Arrays.asList(1,18,12,16,4));
    // 正序
    list.stream().sorted().forEach(System.out::println);	// 1 4 12 16 18
    // 逆序
    list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);	// 18 16 12 4 1
}

distinct() 去重

消除流中重複的元素

public static void main(String[] args) {
    // 數組
    Integer[] arr = {1, 1, 1, 2, 2};
    Arrays.stream(arr).distinct().forEach(System.out::println);	// 1 2
}

filter(Predicate) 過濾器

filter()接收到參數是一個函數,若是函數返回 true,則保留這些元素。返回false 則刪除這些元素

假定有一個數組,保留其中的奇數:

public class Main {
    public static void main(String[] args) {
        // 數組
        Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
        Arrays.stream(arr).filter(Main::Odd).forEach(System.out::println); // 1 3 5 7
    }

    // 奇數返回true
    public static Boolean Odd(int number) {
        return number % 2 == 1;
    }
}

一樣的,filter()也接收正則表達式

public static void main(String[] args) {
    // 數組
    Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
    Arrays.stream(arr).filter(num -> num % 2 == 1).forEach(System.out::println);
}

map()相關 - 映射到元素

  • map(Function):將函數操做應用在輸入流的元素中,並將返回值傳遞到輸出流中。
  • mapToInt(ToIntFunction):操做同上,但結果是 IntStream
  • mapToLong(ToLongFunction):操做同上,但結果是 LongStream
  • mapToDouble(ToDoubleFunction):操做同上,但結果是 DoubleStream
public static void main(String[] args) {
    // 數組
    Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
    Arrays.stream(arr)
        .map(s -> s * 3)    // 每一個元素 × 3
        .forEach(System.out::println);	// 2 4 9 16 10 12 14

}

skip(long n) 跳過前n個元素

skip(long n)能夠省略流的前n個元素

public static void main(String[] args) {
    int[] arr= {1,2,3,4,5};
    // 省略前 3 個
    Arrays.stream(arr).skip(3).forEach(System.out::println);	// 4 5

}

limit(long n) 保留前n個元素

limit(long n) 能夠保留前n個元素,其餘的不要

public static void main(String[] args) {
    int[] arr= {1,2,3,4,5};
    // 保留前 3 個
    Arrays.stream(arr).limit(3).forEach(System.out::println);	// 1 2 3

}

parallel() 流的並行處理

parallel() 可實現多處理器並行操做。實現原理爲將流分割爲多個(一般數目爲 CPU 核心數)並在不一樣處理器上分別執行操做。

使用場景(前提是確保線程的安全):

  • 將文件下載,保存到服務器
  • 遍歷

假設這樣一個場景:有一個商品的分類ld,要根據分類Id,去數據庫找具體的信息(IO操做)

傳統的遍歷

  • 運行時間:732毫秒
public static void main(String[] args) {
    List<Category> list = getCategoryIdList();    // 獲取商品分類的Id
    
	// 普通的遍歷
    long start = System.currentTimeMillis();

    for (Category category : list) {
        System.out.println(getById(category.id));
    }
    long end = System.currentTimeMillis();
    System.out.println("普通遍歷 運行時間:" + (end - start) + "毫秒");
}

image-20200823230332170

使用 parallel()遍歷

  • 遍歷的結果是無序的

  • 運行時間:607毫秒

public static void main(String[] args) {
        List<Category> list = getCategoryIdList();    // 獲取商品分類的Id

        // 並行遍歷
        long start = System.currentTimeMillis();
        
        list.stream().parallel().forEach(category -> {
            // IO 操做
            System.out.println(getById(category.id));	// 根據分類Id,查詢具體信息
        });
        
        long end = System.currentTimeMillis();
        
        System.out.println("parallel() 運行時間:" + (end - start) + "毫秒");

    }

image-20200823230457355

使用 Java8 的 parallel 能夠加快某些操做的速度,但若是是一些簡單的操做,那就得不償失了

此外,parallel() 遍歷的結果是無序的

流的終端操做

終端操做:獲取流的最終結果,沒法再日後傳遞流,好比打印、獲取集合、查找,都算是終端操做

返回 Optional 對象

Optional 類 隱藏了可能存在空指針的不肯定性

一些終端操做,會返回一個Optional 對象,由於這些操做,不能保證預期結果必定存在,也就是隱藏了空指針的信息。若是流是空的,那就會返回Option.empty

  • findFirst() 返回一個包含第一個元素的 Optional 對象,若是流爲空則返回 Optional.empty

  • findAny() 返回包含任意元素的 Optional 對象,若是流爲空則返回 Optional.empty

  • max()min() 返回一個包含最大值或者最小值的 Optional 對象,若是流爲空則返回 Optional.empty

    reduce() 再也不以 identity 形式開頭,而是將其返回值包裝在 Optional 中。

    identity 對象成爲其餘形式的 reduce() 的默認結果,所以不存在空結果的風險)

findFirst()

public static void main(String[] args) {
    String [] str1 = {"java","python","c++"};
    String [] str2 = {};

    System.out.println(Stream.of(str1).findFirst());	// Optional[java]
    System.out.println(Stream.of(str2).findFirst());	// Optional.empty

}

findAny()

public static void main(String[] args) {
    String [] str1 = {"java","python","c++"};
    String [] str2 = {};

    System.out.println(Stream.of(str1).findAny());	// Optional[java]
    System.out.println(Stream.of(str2).findAny());	// Optional.empty

}

max()min()

public static void main(String[] args) {
    Integer[] arr1 = {1, 2, 3, 4, 5};
    Integer[] arr2 = {};

    System.out.println(Stream.of(arr1).max(Integer::compareTo));	// Optional[5]
    System.out.println(Stream.of(arr1).min(Integer::compareTo));	// Optional[5]
    
    System.out.println(Stream.of(arr2).min(Integer::compareTo));	// Optional.empty
    System.out.println(Stream.of(arr2).max(Integer::compareTo));	// Optional.empty

}

解包 Optional:

  • isPresent():判斷 Optional 對象中是否包含元素
  • ifPresent(Consumer):若是 Optional對象中包含元素,就調用 Consumer方法(lambda表達式)
  • get():獲取 Optional對象的 元素
  • orElse(otherObject):若是值存在則直接返回,不然生成 otherObject
  • orElseGet(Supplier):若是值存在則直接返回,不然使用 Supplier 函數生成一個可替代對象。
  • orElseThrow(Supplier):若是值存在直接返回,不然使用 Supplier 函數生成一個異常。

toArray() 返回數組

  • toArray():將流按照數組返回

  • toArray(T[] a):生成自定義類型的數組

生成隨機整型數組:

public static void main(String[] args) {
    int[] arr = new Random().ints(5).toArray();	// 生成生成5個int型,轉換爲int數組
}

toArray(T[] a)將集合轉成數組

public static void main(String[] args) {
        List<String> list = new LinkedList<>();
        list.add("java ");
        list.add("python");

        String[] str = (String[]) list.toArray(new String[0]);

        Stream.of(str).forEach(System.out::print);	// java python
    }

forEach() 遍歷

  • forEach(Consumer)常見如 System.out::println 做爲 Consumer 函數。

  • forEachOrdered(Consumer): 保證 forEach 按照原始流順序操做。(parallel()操做以後是無序的,用這個能夠強制保持原始流的順序)

兩個函數都接收lambda表達式

使用forEach遍歷數組:

Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
// 接收lambda表達式
// i 就是數組中的元素
Arrays.stream(arr).forEach(i -> {
    // do something...
});

傳統方法遍歷數組:

Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
for(Integer i : arr){
    // do something...
}

collect() 返回集合

  • collect(Collector):使用 Collector 收集流元素到結果集合中。

  • collect(Supplier, BiConsumer, BiConsumer):同上,第一個參數 Supplier 建立了一個新的結果集合,第二個參數 BiConsumer 將下一個元素收集到結果集合中,第三個參數 BiConsumer 用於將兩個結果集合合併起來。

生成隨機數,保存到 LinkedList中

public static void main(String[] args) {
    LinkedList<Integer> list = new Random(47)
        .ints(10)
        .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
    	
    
    for (Integer integer : list) {
        System.out.print(integer+ " ");
    }
}

reduce() 組合

使用reduce() 組合全部流中的元素

  • reduce(BinaryOperator):使用 BinaryOperator 來組合全部流中的元素。由於流可能爲空,其返回值爲 Optional

  • reduce(identity, BinaryOperator):功能同上,可是使用 identity 做爲其組合的初始值。所以若是流爲空,identity 就是結果。

  • reduce(identity, BiFunction, BinaryOperator):更復雜的使用形式

    • identity:組合函數的標識值,累加器的初始值。

    • BiFunction:累加器,一個函數,用於將額外的元素合併到結果中

    • BinaryOperator:用於組合兩個值的關聯、不干擾、無狀態函數,必須與累加器函數兼容

      只有在並行流中才會執行

使用Stream.reduce()合併流的元素,併產生單個值

  • 傳統方法使用for循環求和

    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int sum = 0;
        for(int i : numbers){
            sum += i;
        }
        System.out.println(sum); // 55
    
    }
  • 使用reduce() 求和

    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int sum = Arrays.stream(numbers).reduce(0, (a, b) -> a + b);
        System.out.println(sum);
    }

    累加器的初始值設置爲0,(a,b)-> a+b是累加器,結果保存到第一個參數中

count() 統計

統計流中的個數,返回值是long類型

String[] arr = {"123", "456", "789"};
long cnt = Stream.of(arr).count();
System.out.println(cnt);  // 3

max()min() 最大數值

  • max()min():數值流操做無需 Comparator。返回一個Optional對象

數組最大值、最小值

public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    System.out.println(Arrays.stream(arr).max()); 				// OptionalInt[10]
    System.out.println(Arrays.stream(arr).max().getAsInt());	// 10

    
    System.out.println(Arrays.stream(arr).min());				 // OptionalInt[1]
    System.out.println(Arrays.stream(arr).min().getAsInt());	// 1

}

max(Comparator)min(Comparator)

  • max(Comparator):根據所傳入的 Comparator 所決定的「最大」元素。
  • min(Comparator):根據所傳入的 Comparator 所決定的「最小」元素。
  • 返回的都是Optional對象
public static void main(String[] args) {
        String[] str = {"java","c++","python"};
        System.out.println(Arrays.stream(str).max(String::compareTo));  // Optional[python]
        System.out.println(Arrays.stream(str).max(String::compareTo).get());	// python
    }

sum() 求和

求流元素的和

public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int sum = Arrays.stream(arr).sum();
    System.out.println(sum);		// 55
}

average() 平均值

求流元素的平均值,返回一個OptionalDouble對象

public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    OptionalDouble average = Arrays.stream(arr).average();
    System.out.println(average);				// OptionalDouble[5.5]
    System.out.println(average.getAsDouble());	// 5.5
}

匹配

  • allMatch(Predicate) :若是流的每一個元素提供給 Predicate 都返回 true ,結果返回爲 true。在第一個 false 時,則中止執行計算。
  • anyMatch(Predicate):若是流的任意一個元素提供給 Predicate 返回 true ,結果返回爲 true。在第一個 true 時中止執行計算。
  • noneMatch(Predicate):若是流的每一個元素提供給 Predicate 都返回 false 時,結果返回爲 true。在第一個 true 時中止執行計算。

說簡單,就是傳一個lambda表達式,而後返回布爾值

判斷數組中是否全都爲奇數

public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // 判斷全部元素是否都爲奇數
    boolean flag = Arrays.stream(arr).allMatch(i -> i % 2 == 1);
    System.out.println(flag);
}

查找

  • findFirst():返回第一個流元素的 Optional,若是流爲空返回 Optional.empty
  • findAny(:返回含有任意流元素的 Optional,若是流爲空返回 Optional.empty
public static void main(String[] args) {
    String[] str = {"java","c++","python"};
    System.out.println(Arrays.stream(str).findFirst());			// Optional[java]
    System.out.println(Arrays.stream(str).findFirst().get());	// Java
}
相關文章
相關標籤/搜索