玩轉Java8中的 Stream 之從零認識 Stream

相信Java8的Stream 大家都已聽說過了,但是可能大家不會用或者用的不熟,文章將帶大家從零開始使用,循序漸進,帶你走向Stream的巔峯。

操作符

什麼是操作符呢?操作符就是對數據進行的一種處理工作,一道加工程序;就好像工廠的工人對流水線上的產品進行一道加工程序一樣。

Stream的操作符大體上分爲兩種:中間操作符和終止操作符

中間操作符

對於數據流來說,中間操作符在執行制定處理程序後,數據流依然可以傳遞給下一級的操作符。

中間操作符包含8種(排除了parallel,sequential,這兩個操作並不涉及到對數據流的加工操作):

  • map(mapToInt,mapToLong,mapToDouble) 轉換操作符,把比如A->B,這裏默認提供了轉int,long,double的操作符。

  • flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 變成 2,3,4 也就是從原來的一個數據變成了3個數據,這裏默認提供了拍平成int,long,double的操作符。

  • limit 限流操作,比如數據流中有10個 我只要出前3個就可以使用。

  • distint 去重操作,對重複元素去重,底層使用了equals方法。

  • filter 過濾操作,把不想要的數據過濾。

  • peek 挑出操作,如果想對數據進行某些操作,如:讀取、編輯修改等。

  • skip 跳過操作,跳過某些元素。

  • sorted(unordered) 排序操作,對元素排序,前提是實現Comparable接口,當然也可以自定義比較器。

終止操作符

數據經過中間加工操作,就輪到終止操作符上場了;終止操作符就是用來對數據進行收集或者消費的,數據到了終止操作這裏就不會向下流動了,終止操作符只能使用一次。

  • collect 收集操作,將所有數據收集起來,這個操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以說Stream 的核心在於Collectors。

  • count 統計操作,統計最終的數據個數。

  • findFirst、findAny 查找操作,查找第一個、查找任何一個 返回的類型爲Optional。

  • noneMatch、allMatch、anyMatch 匹配操作,數據流中是否存在符合條件的元素 返回值爲bool 值。

  • min、max 最值操作,需要自定義比較器,返回數據流中最大最小的值。

  • reduce 規約操作,將整個數據流的值規約爲一個值,count、min、max底層就是使用reduce。

  • forEach、forEachOrdered 遍歷操作,這裏就是對最終的數據進行消費了。

  • toArray 數組操作,將數據流的元素轉換成數組。

這裏只介紹了Stream,並沒有涉及到IntStream、LongStream、DoubleStream,這三個流實現了一些特有的操作符,我將在後續文章中介紹到。Java知音公衆號內回覆“面試題聚合”,送你一份各大公司面試彙總寶典。

說了這麼多,只介紹這些操作符還遠遠不夠;俗話說,實踐出真知。那麼,Let‘s go。

代碼演練

Stream 的一系列操作必須要使用終止操作,否者整個數據流是不會流動起來的,即處理操作不會執行。

  • map,可以看到 map 操作符要求輸入一個Function的函數是接口實例,功能是將T類型轉換成R類型的。

map操作將原來的單詞 轉換成了每個單的長度,利用了String自身的length()方法,該方法返回類型爲int。這裏我直接使用了lambda表達式,關於lambda表達式 還請讀者們自行了解吧。

public class Main {

    public static void main(String[] args) {
        Stream.of("apple","banana","orange","waltermaleon","grape")
                .map(e->e.length()) //轉成單詞的長度 int
                .forEach(e->System.out.println(e)); //輸出
    }
}

當然也可以這樣,這裏使用了成員函數引用,爲了便於讀者們理解,後續的例子中將使用lambda表達式而非函數引用。

public class Main {

    public static void main(String[] args) {
         Stream.of("apple","banana","orange","waltermaleon","grape")
                .map(String::length) //轉成單詞的長度 int
                .forEach(System.out::println);
    }
}

結果如圖:

  • mapToInt 將數據流中得元素轉成Int,這限定了轉換的類型Int,最終產生的流爲IntStream,及結果只能轉化成int。

 

 

public class Main {

    public static void main(String[] args) {
         Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToInt(e -> e.length()) //轉成int
                .forEach(e -> System.out.println(e));
    }
}

mapToInt如圖:

  • mapToLong、mapToDouble 與mapToInt 類似

     

public class Main {

    public static void main(String[] args) {
         Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToLong(e -> e.length()) //轉成long ,本質上是int 但是存在類型自動轉換
                .forEach(e -> System.out.println(e));
    }
}

mapToLong 如圖:

public class Main {

    public static void main(String[] args) {
         Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .mapToDouble(e -> e.length()) //轉成Double ,自動類型轉換成Double
                .forEach(e -> System.out.println(e));
    }
}

mapToDouble如圖:

  • flatmap 作用就是將元素拍平拍扁 ,將拍扁的元素重新組成Stream,並將這些Stream 串行合併成一條Stream

 

 

public class Main {

    public static void main(String[] args) {
        Stream.of("a-b-c-d","e-f-i-g-h")
                .flatMap(e->Stream.of(e.split("-")))
                .forEach(e->System.out.println(e));

    }
}

flatmap 如圖:

  • flatmapToInt、flatmapToLong、flatmapToDouble 跟flatMap 都類似的,只是類型被限定了,這裏就不在舉例子了。

  • limit 限制元素的個數,只需傳入 long 類型 表示限制的最大數

public class Main {

    public static void main(String[] args) {
        Stream.of(1,2,3,4,5,6)
                .limit(3) //限制三個
                .forEach(e->System.out.println(e)); //將輸出 前三個 1,2,3
    }
}

limit如圖:

public class Main {

    public static void main(String[] args) {

        Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
                .distinct() //去重
                .forEach(e->System.out.println(e));

    }
}

distinct 如圖:

  • filter 對某些元素進行過濾,不符合篩選條件的將無法進入流的下游

     

public class Main {

    public static void main(String[] args) {
        Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
                .filter(e->e>=5) //過濾小於5的
                .forEach(e->System.out.println(e));
    }
}

filter 如圖:

  • peek 挑選 ,將元素挑選出來,可以理解爲提前消費

     

public class Main {

    public static void main(String[] args) {

        User w = new User("w",10);
        User x = new User("x",11);
        User y = new User("y",12);

        Stream.of(w,x,y)
                .peek(e->{e.setName(e.getAge()+e.getName());}) //重新設置名字 變成 年齡+名字
                .forEach(e->System.out.println(e.toString()));

    }

    static class User {

        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        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 "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

}

peek 如圖:

  • skip 跳過 元素

     

public class Main {

    public static void main(String[] args) {
        Stream.of(1,2,3,4,5,6,7,8,9)
                .skip(4) //跳過前四個
                .forEach(e->System.out.println(e)); //輸出的結果應該只有5,6,7,8,9
    }
}

skip 如圖:

  • sorted 排序 底層依賴Comparable 實現,也可以提供自定義比較器

這裏Integer 實現了比較器

public class Main {

    public static void main(String[] args) {
        Stream.of(2,1,3,6,4,9,6,8,0)
                .sorted()
                .forEach(e->System.out.println(e));
    }
}

sorted 默認比較器如圖:

這裏使用自定義比較,當然User 可以實現Comparable 接口

public class Main {

    public static void main(String[] args) {

        User x = new User("x",11);
        User y = new User("y",12);
        User w = new User("w",10);

        Stream.of(w,x,y)
                .sorted((e1,e2)->e1.age>e2.age?1:e1.age==e2.age?0:-1)
                .forEach(e->System.out.println(e.toString()));

    }

    static class User {

        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        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 "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

}

如圖:

  • collect 收集,使用系統提供的收集器可以將最終的數據流收集到List,Set,Map等容器中。

這裏我使用collect 將元素收集到一個set中

public class Main {

    public static void main(String[] args) {
        Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .collect(Collectors.toSet()) //set 容器
                .forEach(e -> System.out.println(e));
    }
}

咦?,不是說終止操作符只能使用一次嗎,爲什麼這裏調用了forEach 呢?forEach不僅僅是是Stream 中得操作符還是各種集合中得一個語法糖,不信咋們試試。Java知音公衆號內回覆“面試題聚合”,送你一份各大公司面試彙總寶典。

public class Main {

    public static void main(String[] args) {

        Set<String> stringSet = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .collect(Collectors.toSet()); //收集的結果就是set
        stringSet.forEach(e->System.out.println(e)); set的語法糖forEach
}

結果如圖:

  • count 統計數據流中的元素個數,返回的是long 類型

     

public class Main {

    public static void main(String[] args) {

        long count = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .count();

        System.out.println(count);
    }
}

count 如圖:

  • findFirst 獲取流中的第一個元素

這裏找到第一個元素 apple

public class FindFirst {

    public static void main(String[] args) {
        Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .findFirst();
        stringOptional.ifPresent(e->System.out.println(e));
    }
}

findFirst 結果如圖:

  • findAny 獲取流中任意一個元素

     

public class FindAny {

    public static void main(String[] args) {
        Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
                .parallel()
                .findAny(); //在並行流下每次返回的結果可能一樣也可能不一樣
        stringOptional.ifPresent(e->System.out.println(e));
    }
}

findAny 在並行流下 使用結果:

輸出了orange

輸出了banana

  • noneMatch 數據流中得沒有一個元素與條件匹配的

這裏 的作用是是判斷數據流中 一個都沒有與aa 相等元素 ,但是流中存在 aa ,所以最終結果應該是false

public class NoneMatch {

    public static void main(String[] args) {
        boolean result = Stream.of("aa","bb","cc","aa")
                .noneMatch(e->e.equals("aa"));
        System.out.println(result);
    }
}

noneMatch 如圖:

  • allMatch和anyMatch 一個是全匹配,一個是任意匹配 和noneMatch 類似,這裏就不在舉例了。

  • min 最小的一個,傳入比較器,也可能沒有(如果數據流爲空)

public class Main {

    public static void main(String[] args) {

        Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
                .min((e1,e2)->e1.compareTo(e2));

        integerOptional.ifPresent(e->System.out.println(e));

    }

min如圖:

  • max 元素中最大的,需要傳入比較器,也可能沒有(流爲Empty時)

     

public class Main {

    public static void main(String[] args) {

        Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
                .max((e1,e2)->e1.compareTo(e2));

        integerOptional.ifPresent(e->System.out.println(e));

    }
}

max 如圖:

  • reduce 是一個規約操作,所有的元素歸約成一個,比如對所有元素求和,乘啊等。

這裏實現了一個加法,指定了初始化的值

public class Main {
    public static void main(String[] args) {

        int sum = Stream.of(0,9,8,4,5,6,-1)
              .reduce(0,(e1,e2)->e1+e2);
        System.out.println(sum);
    }
}

reduce 如圖:

  • forEach

forEach 其實前就已經見過了,對每個數據遍歷迭代

  • forEachOrdered 適用用於並行流的情況下進行迭代,能保證迭代的有序性

這裏通過並行的方式輸出數字

public class ForEachOrdered {
    public static void main(String[] args) {
        Stream.of(0,2,6,5,4,9,8,-1)
                .parallel()
                .forEachOrdered(e->{
                    System.out.println(Thread.currentThread().getName()+": "+e);});
    }
}

forEachOrdered 如圖:

  • toArray 轉成數組,可以提供自定義數組生成器

     

public class ToArray {
    public static void main(String[] args) {
        Object[] objects=Stream.of(0,2,6,5,4,9,8,-1)
                .toArray();

        for (int i = 0; i < objects.length; i++) {
            System.out.println(objects[i]);
        }
    }
}

toArray 如圖:

總結

Java8 Stream就帶大家認識到這裏,如果你能跟着我的文章把每一個例子都敲一遍,相信都能掌握這些操作符的初步用法。

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