Java8新特性3:Stream2—一文詳解Stream API,讓你快速理解Stream Api提供的諸多常用方法

本文主要是帶你認識Stream Api 原理,理解Stream Api使用,並學會從多種數據源生成Stream,以操作數據集;同時帶你快速理解和學會Stream API中 Filter、skip、limit、map、flatMap、Find、reduce、match等方法的使用。 

一、利用Stream Api 提供的接口和方法,生成一個流

要想在開發中利用Stream的高效特性處理數據,我們就要先生成一個流,《java8 in action》中提到Stream Api爲我們提供了集中生成流的方法:

現在我們就舉例看看如何從集合、序列值、數組、文件、生成函數來創建一個Stream 流,代碼和解析都在裏面。

package com.aigov.java8_newfeatures.stream;

import lombok.Data;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * @author : aigoV
 * @date :2019/10/23
 **/
public class CreateStream {

    //使用Stream Api 從集合生成流
    private static Stream<String> createStreamFromCollection(){
        List<String> list = Arrays.asList("A", "I", "G", "O", "V");
        return  list.stream();
    }

    //使用Stream Api 從一個顯示的Value(它的元素可是任意類型,數量也可以是任意個)生成一個Stream
    private static Stream<String> createStreamFromValue(){
        return Stream.of("Java 8 ", "Stream ", "In ", "Action");
    }

    //使用Stream Api 從一個數組創建一個Stream 這裏是個int型Stream
    private static IntStream createStreamFromArrays(){
        int[] intArray = {1,2,3,4,5};
        return Arrays.stream(intArray);
    }

    /**
     * 使用Stream Api 從一個文件生成一個Stream
     * Java8中用於處理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。
     * java.nio.file.Files中的很多靜態方法都會返回一個流。如:Files.lines,它會返回一個由指定文件中的各行構成的字符串流。
     */
    private static Stream<String> createStreamFromFile(){
        Path path = Paths.get("E:\\DevelopmentSoftware\\cptmcp\\aigovProject\\java8_newfeatures\\src\\main\\java\\com\\aigov\\java8_newfeatures\\stream\\SimpleStream.java");
        Stream<String> filesStream = null;
        try {
            filesStream = Files.lines(path);//Files.lines得到一個流,其中的每個元素都是給定文件中的一行
        } catch (IOException e) {
            e.printStackTrace();
        }
        return filesStream;
    }

    /**
     * Stream API提供了兩個靜態方法來從函數生成無限流:Stream.iterate()和Stream.generate()。
     *
     * 用Stream.iterate生成一個無限流:
     * iterate方法接受一個初始值(在這裏是0),還有一個依次應用在每個產生的新值上UnaryOperator<t>類型的
     * Lambda。這裏,我們使用Lambda n -> n + 2,返回的是前一個元素加上2。
     * 流的第一個元素是初始值0。然後加上2來生成新的值2,再加上2來得到新的值4,以此類推。
     * iterate是順序執行的,因爲結果取決於前一次計算,且iterate是無限執行下去的,及其產生的流也是無界的。
     * 所以我們需要使用limit方法來顯式限制流的大小,這裏只選擇了前5個偶數。
     */
    private static Stream<Integer> createStreamFromIterate(){
        return Stream.iterate(0,n->n + 2).limit(5);
    }

    /**
     * 用Stream.generate()生成一個簡單的無限流:
     *
     * Stream.generate()接受一個supplier接口類型的參數,這個參數類型本身是無狀態的,
     * 即,它不會記錄任何可以在之後的計算中使用的值(和Iterate相反),比如下面這個例子:
     */
    private static Stream<Double> createStreamFromGenerate(){
        return Stream.generate(Math::random).limit(5);
    }

    /**
     * Stream.generate() :
     * 我們也可自己實現supplier接口,自己構建一個有狀態的supplier類,作爲參數傳進去。
     * 這樣的話,我們就是可以實現Iterate相似的作用。
     * 但是這樣的代碼在程序並行執行時是不安全的,所以我們還是推薦iterate方法
     *
     * 下面我們寫一個有狀態的案例,對比一下iterate方法
     */


    //創建一個obj對象
    @Data
    static class Obj {
        private int id;
        private String name;

        public Obj(int id, String name) {
            this.id = id;
            this.name = name;
        }

        @Override
        public String toString() {
            return "Obj{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    //新建一個Supplier的實現類 使你自己的Supplier對象有可變的狀態(記錄前一箇舊的Obj對象和後來的新的Obj對象)
    static class ObjSupplier implements Supplier<Obj> {
        private int index = 0;
        private Random random = new Random(System.currentTimeMillis());

        @Override
        public Obj get() {
            index = random.nextInt(100);
            return new Obj(index, "名稱" + index);
        }
    }

    //Generate創建一個對象流
    private static Stream<Obj> createObjStreamFromGenerate() {
        return Stream.generate(new ObjSupplier()).limit(5);
    }


    public static void main(String[] args) {
        //createStreamFromCollection().forEach(System.out::println);
        //createStreamFromValue().map(String::toUpperCase).forEach(System.out::println);
        //System.out.println(createStreamFromArrays().sum());//String.sum將數組求和
        //createStreamFromFile().forEach(System.out::println);//這會把SimpleStream.java文件裏面的代碼包括格式全部打印出來
        //createStreamFromIterate().forEach(System.out::println);
        //createStreamFromGenerate().forEach(System.out::println);
        createObjStreamFromGenerate().forEach(System.out::println);
    }
}

二、案例詳解Stream API中 Filter、skip、limit、map、flatMap、Find、reduce、match等方法的原理與使用

本來打算直接把我寫的源碼和註釋丟上來省事,但註釋太多,idea看着還好,直接貼上博客看可能眼花:

下面給出的所有案例解析,都將基於以下基礎代碼:

package com.aigov.java8_newfeatures.stream;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;

/**
 * @author : aigoV
 * @date :2019/10/24
 * Stream api:
 **/
public class StreamApiMethod {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 6, 6, 7, 8, 1);
        List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 530, Dish.Type.OTHER),
                new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("season fruit", true, 120, Dish.Type.OTHER),
                new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("prawns", false, 300, Dish.Type.FISH),
                new Dish("salmon", false, 450, Dish.Type.FISH));
        /**

        案例啦。。。    

        **/

    }
}

開始。。。。。。。。。。。。。。。。。。。。。。。

1、Stream().filter()作用與使用:

查看filter Api 我們會發現,它接受的參數是: Predicate<? super T> (這是一個函數,這個函數裏面是一個抽象方法 boolean test(T t),該方法返回一個布爾值。) filter方法作用是返回 包含所有符合Predicate函數規則(規則你定)的元素的一個Stream流。下面舉個栗子:

list.stream().filter(a -> a % 2 == 0).forEach(System.out::print);//返回流中的偶數

2、stream().distinct()作用與使用

 distinct方法的作用是去重,並返回去重後的Stream流。舉個栗子:

list.stream().distinct().forEach(System.out::print);

3、stream().skip()作用與使用:

skip,字面意思跳過。skip(n)方法,返回一個扔掉了流中前n個元素的流。如果流中元素不足n個,則返回一個空流。舉個栗子:

list.stream().skip(3).forEach(System.out::print);

4、stream().limit()作用與使用:

 limit(n)返回一個不超過給定長度n的流,舉個栗子:

list.stream().limit(2).forEach(System.out::print);

5、關於stream().map() 與stream().flatMap()如何使用,有何區別?

stream().map():接受的參數是Function<? superx T, ? extends R>函數,該函數接受一個泛型T,返回一個R泛型的值。即,map可以讓你將一個對象轉化成其他的對象,比如integer轉爲String,小寫字母轉爲大寫等,也就是它要創建一個新版本的數據
stream().flatMap():接受的參數是Function<? super T, ? extends Stream<? extends R>>函數,與map不同的是該函數的第二個參數是個Stream。

flatMap()與map()差別在於:
Map()可能生成的是一個數組流或者一個流的列表(多個小流組成的一個大流),而flatMap()則是把map()生成的多個獨立的流(數組流)合成一個流。 比如map(xx)返回的結果爲流1(元素爲2,3),流2(元素爲3,6),這種情況下使用flapMap(xx)的結果就是:流(2,3,3,6)。二者結果的區別將決定後續操作的難易。
下面舉兩個一看就懂的栗子對比說明 stream().map() 與stream().flatMap()的使用和區別:

5.1、stream().map()的基本使用:

list.stream().map(a -> a + 1).forEach(System.out::print);//map操作,把每個元素都+1再輸出
List<String> collect = menu.stream().map(m -> m.getName()).collect(toList());//map操作,獲得菜單集中的菜名,並生成一個流
System.out.println(collect);

5.2、stream().map()難以將String數組轉爲一個字符流 即不能這樣:{"Goodbye", "World"} -> {G,o,o,d,b,y,e,W,o,r,l,d}。看下面案例解析:

String[] arrayOfWords = {"Goodbye", "World"};
Stream<String[]> arrayStream = Arrays.stream(arrayOfWords)
                //split方法分割每個單詞的字符,map得到一個是Stream<String[]> 類型的數組流
                .map(s -> s.split(""));

Stream<Stream<String>> streamStream = arrayStream
                //想得到一個Stream<String>字符流,所以嘗試再一次對上面結果進行map,讓每個數組變成一個單獨的流;
                //結果卻是得到了一個流的列表,即,這個流是由多個單獨的流組成的,顯然這不是我們想要的,我們也無法進行去重。
                .map(Arrays::stream);

5.3、stream().flatMap() 可以把map()生成的數組流合成一個流

 Stream<String> stringStream = Arrays.stream(arrayOfWords)
                .map(m -> m.split(""))
                //此處得到的是Stream<String>類型的一個字符流,可以輕易地進行許多後續操作,比如去重
                .flatMap(Arrays::stream);

stringStream.distinct().forEach(System.out::print);//結果:GodbyeWrl

Optional 容器類

再說find方法前,我們先大概瞭解一個類:Optional和它的一些方法,後面會出博文專門解釋這個類。
Optional<T> class (java.util.Optional)是一個容器類,代表一個值存在或不存在,比如下面findFirst也可能找不到值
來看看Optional容器的常用方法:

  •  isPresent():Optional包含有值的時候返回true, 否則返回false。
  • ifPresent(Consumer<T> block):會在值存在的時候執行給定的代碼塊。
  • T get():會在值存在時返回值,否則拋出一個NoSuchElement異常。
  • T orElse(T other):會在值存在時返回值,否則返回一個默認值。

6、Find

findFirst:作用找到流的第一個元素,它的返回值是一個Optional<T>類
findAny:作用返回當前流中的任意元素,它的返回值是一個Optional<T>類
 findFirst與findAny區別:

  • 在串行的流中,findAny和findFirst返回的都是第一個對象;
  • 而在並行流(parallelStream())中,findAny返回的是最快處理完的那個線程的數據。
  • 並行操作中,findAny的效率會比findFirst要快

看下面例子就懂了:

Optional<Integer> first = list.stream().findFirst();
System.out.println(first.get());//輸出:1

/**找到list(流)中的任意一個偶數**/
Integer integer = list.stream().filter(t -> t % 2 == 0).findAny().get();//得到是第一個偶數2,和findFist結果一樣
System.out.println(integer);
Integer integer1 = list.parallelStream().filter(t -> t % 2 == 0).findAny().get();//得到的是偶數6
System.out.println(integer1);

7、match

allMatch:作用是判斷流中的元素是否都能匹配給定的Predicate<? super T> 函數規則,返回Boolean
anyMatch:作用是判斷流中是否有一個元素能匹配給定的Predicate<? super T> 函數規則,返回Boolean
noneMatch:確保流中沒有任何元素與給定的Predicate<? super T> 函數規則匹配 ,返回Boolean

boolean b = menu.stream().anyMatch(Dish::isVegetarian);//判斷流中是否有素食(Vegetarian)
//System.out.println(b);//true
boolean b1 = menu.stream().allMatch(d -> d.getCalories() < 400);//判斷是否所有菜的熱量都低於400卡路里
//System.out.println(b1);//false
boolean b2 = menu.stream().noneMatch(Dish::isVegetarian);//判斷流中是否不能存在素食
//System.out.println(b2);//false

8、reduce

reduce作用是將流中所有元素反覆結合起來,進行更復雜的查詢,進而得到一個值,比如“計算int集合裏所有數字相加的和”或“找出int集合中的最大數字”
reduce接受兩個參數:

  • 第一個是初始值;
  • 第二個是BinaryOperator<T> 函數

圖解reduce下有初始值情況下的求和流程:

8.1 有初始值情況下(0也是初始值哦),求和

Integer reduce = list.stream().reduce(0, (s, w) -> s + w);//對list集合元素進行求和
Integer reduce1 = list.stream().reduce(0, Integer::sum);//等同於上式

8.2 無初始值情況下,求和

這裏爲什麼它返回一個Optional<Integer>呢?因爲考慮到流中沒有任何元素的情況,reduce操作無法返回其和,因爲它沒有初始值。結果被包裹在一個Optional對象裏,以表明和可能不存在,處理結果爲null的情況

Optional<Integer> sum = list.stream().reduce(Integer::sum);

8.3 求最大值和最小值

Optional<Integer> max = list.stream().reduce(Integer::max);
Optional<Integer> min = list.stream().reduce(Integer::min);

三、小結

比較常用的一些流操作彙總

 

java8 新特性解析(更新中):

Java8新特性1:lambda表達式入門--由淺入深,從單發步槍邁向自動步槍 

Java8新特性2:方法引用--深入理解雙冒號::的使用

Java8新特性3:Stream1——什麼是Stream,Stream的特性,如何使用Stream,Stream與Collection集合的區別

Java8新特性3:Stream2—一文詳解Stream API,讓你快速理解Stream Api提供的諸多常用方法

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章