夯實Java基礎(二十四)——Java8新特徵之Stream API

一、Stream簡介

Java8中除了引入了好用的Lambda表達式、Date API以外,另外還有一大亮點就是Stream API了,也是最值得全部Java開發人員學習的一個知識點,由於它的功能很是的強大,尤爲是和前面學習的Lambda表達式、函數式接口、方法引用共同使用的時候。html

Stream流是數據渠道,用於操做數據源所生成的元素序列,即操做集合、數組等等。其中集合和stream的區別是:集合講的是數據,而stream講的是計算Stream的API所有都位於java.util.stream這樣一個包下,它可以幫助開發人員從更高的抽象層次上對集合進行一系列操做,就相似於使用SQL執行數據庫查詢。而藉助java.util.stream包,咱們能夠簡明的聲明性的表達集合,數組和其餘數據源上可能的並行處理。實現從外部迭代到內部迭代的改變。它含有高效的聚合操做、大批量的數據處理,同時也內置了許多運算方式,包括篩選、排序、聚合等等。簡單來講,用Stream來操做集合——減小了代碼量,加強了可讀性,提升運行效率。java

而後這裏要提一下:Stream和Java IO中的stream是徹底不一樣概念的兩個東西。本文要講解的stream是可以對集合對象進行各類串行或併發彙集操做,而Java IO流用來處理設備之間的數據傳輸,它們二者大相徑庭。數據庫

Stream的主要特色有:
一、stream自己並不存儲數據,數據是存儲在對應的collection(集合)裏,或者在須要的時候才生成的。
二、stream不會修改數據源,老是返回新的stream。
三、stream的操做是延遲(lazy)執行的:僅當最終的結果須要的時候纔會執行,即執行到終止操做爲止。數組

二、傳統方式與使用Stream簡單對比

首先咱們先使用之前的方法來對集合進行一些操做,代碼示例以下:網絡

public class StreamTest {
    public static void main(String[] args) {
        //建立集合
        ArrayList<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");
        list.add("four");
        list.add("five");
        list.add("six");

        //遍歷數據,只遍歷4個
        for (int i = 0; i < list.size()-1; i++) {
                //過濾掉字符串——two
                if (!(list.get(i).contains("two"))) {
                    System.out.println(list.get(i));
            }
        }
    }
}

我相信像上面這樣的代碼估計每一個java開發人員分分鐘均可實現。但若是要求再複雜一點,代碼量就會大量增長,因此再經過建立一個存儲數據的集合,而後對集合進行遍歷,再經過特定的條件進行篩選而且將結果輸出。這樣的代碼中或多或少會有缺點的:
一、代碼量一多,維護確定更加困難。可讀性也會變差。併發

二、難以擴展,要修改成並行處理估計會花費很大的精力。app

而後下面展現了用Stream來實現相同的功能,很是的簡潔,若是須要添加其餘的方法也很是的方便。注:Stream中的一些方法可能暫時不清楚,這不要緊,後面都會一 一介紹它們。dom

//使用filter,limit,forEach方法
list.stream().filter(s->!s.contains("two")).limit(4).forEach(System.out::println);

最終兩種方式的代碼運行結果是同樣的:ide

image

從上面使用Stream的狀況下咱們能夠發現是Stream直接鏈接成了一個串,而後將stream過濾後的結果轉爲集合並返回輸出。而至於轉成哪一種類型,這由JVM進行推斷。整個程序的執行過程像是在敘述一件事,沒有過多的中間變量,因此能夠減少GC壓力,提升效率。因此接下來開始探索Stream的功能吧。函數

三、Stream操做的三個步驟

Stream的原理是:將要處理的元素看作一種流,流在管道中傳輸,而且能夠在管道的節點上處理,包括有過濾、篩選、去重、排序、彙集等。元素在管道中通過中間操做的處理,最後由終止操做獲得前面處理的結果。

因此Stream的全部操做分爲這三個部分:建立對象-->中間操做-->終止操做,以下圖。

image

(1)、建立Stream:一個數據源(如:集合、數組),獲取一個流。

(2)、中間操做:一箇中間操做鏈,對數據源的數據進行處理。

(3)、終止操做(終端操做):一個終止操做,執行中間操做鏈,併產生結果。

四、建立Stream對象

①、使用Java8中被擴展的Collection接口,其中提供了兩個獲取流的方法:

  • default Stream<E> stream() 返回一個數據流
  • default Stream<E> parallelStream() 返回一個並行流
        //一、方法一:經過Collection
        ArrayList<String> list=new ArrayList<>();
        //獲取順序流,即有順序的獲取
        Stream<String> stream = list.stream();
        //獲取並行流,即同時取數據,無序
        Stream<String> stream1 = list.parallelStream();

②、使用Java8 中Arrays的靜態方法stream()獲取數組流:

  • public static <T> Stream<T> stream(T[] array) 返回一個流
        //二、方法二:經過數組
        String str[]=new String[]{"A","B","C","D","E"};
        Stream<String> stream2 = Arrays.stream(str);

③、使用Stream類自己的靜態方法of(),經過顯式的賦來值建立一個流,它能夠接受任意數量的參數:

  • public static<T> Stream<T> of(T... values) 返回一個流
        //三、方法三:經過Stream的靜態方法of()
        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6);

④、建立無限流(迭代、生成)

  • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  • public static<T> Stream<T> generate(Supplier<T> s)
        //4.、方式四:建立無限流(迭代、生成)
        //迭代(須要傳入一個種子,也就是起始值,而後傳入一個一元操做)
        Stream.iterate(2, (x) -> x * 2).limit(10).forEach(System.out::println);
        //生成(無限產生對象)
        Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);

以上的四種方式學習前三中便可,第四種不經常使用。除了上面這些額外還有其餘建立Stream對象的方式,如Stream.empty():建立一個空的流、Random.ints():隨機數流等等。

五、Stream中間操做

中間操做的全部操做會返回一個新的流,但它不會修改原始的數據源,而且操做是延遲執行的(lazy),意思就是在終止操做開始的時候才中間操做才真正開始執行。

中間操做主要有如下方法:filter、limit、 skip、distinct、map (mapToInt, flatMap 等)、 sorted、 peek、 parallel、 sequential、 unordered等。

爲了更好地舉例,咱們先建立一個Student類:

public class Student {
    private int id;
    private String name;
    private int age;
    private String address;

    public Student() { }

    public Student(int id, String name, int age, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    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;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id &&
                age == student.age &&
                Objects.equals(name, student.name) &&
                Objects.equals(address, student.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, address);
    }

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

後面的全部測試數據用會用下面這一組數據(必要時會改):

        List<Student> students= Arrays.asList(
                new Student(1, "張三", 18, "北京"),
                new Student(2, "李四", 19, "上海"),
                new Student(3, "王五", 20, "廣州"),
                new Student(4, "趙六", 21, "浙江"),
                new Student(5, "孫七", 22, "深圳")
        );

而後開始介紹流的中間操做的方法使用:

①、filter(Predicate<? super T> predicate):篩選:接收 Lambda表達式,表示從流中過濾某些元素。

//篩選年齡大於19歲而且住在浙江的學生
students.stream().filter(s -> s.getAge() > 19).filter(s -> "浙江".equals(s.getAddress())).forEach(System.out::println);

運行結果爲:

image

這裏咱們建立了五個學生對象,而後通過filter的篩選,篩選出年齡大於19歲而且家庭住址是在浙江的學生集合。

 

②、limit(long maxSize):截斷:表示使其元素不超過給定數量。

        //給定獲取3個數據
        students.stream().limit(3).forEach(System.out::println);

運行結果:

image

咱們只讓它截斷3個,每次都是從第一個數據開始截取。

 

③、skip(long n):跳過:表示跳過前 n 個元素。若流中元素不足 n 個,則返回一個空流。它與 limit(n)互補。

        //跳過前3個數據
        students.stream().skip(3).forEach(System.out::println);

運行結果:

image

能夠發現輸出的數據剛好與limit方法互補。

 

④、distinct():篩選:去除流中重複的元素。

這裏我將第二個數據改爲和第一個同樣,看結果會怎樣。

    public static void main(String [] args) {
        List<Student> students= Arrays.asList(
                new Student(1, "張三", 18, "北京"),
                new Student(1, "張三", 18, "北京"),
                new Student(3, "王五", 20, "廣州"),
                new Student(4, "趙六", 21, "浙江"),
                new Student(5, "孫七", 22, "深圳")
        );
        //去除重複元素
        students.stream().distinct().forEach(System.out::println);

    }
}

運行結果:

image

能夠發現相同的元素被去除了,可是注意:distinct 須要實體中重寫hashCode()和 equals()方法纔可使用。

 

⑤、map(Function<? super T, ? extends R> mapper):映射(轉換):將一種類型的流轉換爲另一種類型的流。

爲了便於理解咱們來分析一下,map函數中須要傳入一個實現Function<T,R>函數式接口的對象,而該接口的抽象方法apply接收T類型,返回是R類型,因此map也能夠理解爲映射關係。

image

        //lambda表達式
        students.stream().map(s->s.getName()).forEach(System.out::println);
        //方法引用
        students.stream().map(Student::getName).forEach(System.out::println);

運行結果:

image

上面的例子中將Student對象轉換爲了普通的String對象,獲取Student對象的名字。

 

⑥、flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):轉換合併:將流中的每一個值都轉換成另外一個流,而後把全部流組合成一個流而且返回。

它和map有點相似。flatMap在接收到Stream後,會將接收到的Stream中的每一個元素取出來放入另外一個Stream中,最後將一個包含多個元素的Stream返回。

image

        List<Student> student1= Arrays.asList(
                new Student(1, "張三", 18, "北京"),
                new Student(2, "李四", 19, "上海")
        );

        List<Student> student2= Arrays.asList(
                new Student(3, "王五", 20, "廣州"),
                new Student(4, "趙六", 21, "浙江"),
                new Student(5, "孫七", 22, "深圳")
        );

        //lambda
        Stream.of(student1,student2).flatMap(student -> student.stream()).forEach(System.out::println);
        //方法引用
        Stream.of(student1,student2).flatMap(List<Student>::stream).forEach(System.out::println);
        //常規方法
        Stream.of(student1,student2).flatMap(new Function<List<Student>, Stream<?>>() {
            @Override
            public Stream<?> apply(List<Student> students) {
                return students.stream();
            }
        }).forEach(System.out::println);

運行結果都是:

image

由於flatMap中最後須要將每一個元素組合成一個流,因此flatMap方法形參返回了一個stream流。

若是map和flatmap還不清楚能夠參考這篇博客:java8 stream流操做的flatMap(流的扁平化) 寫的很清楚。

 

⑦、sorted():天然排序:按天然順序排序的流元素。

這裏就不用Student對象做爲舉例了,不然要在Student類中實現Comparable接口。

        //天然排序
        List<String> list = Arrays.asList("CC", "BB", "EE", "AA", "DD");
        list.stream().sorted().forEach(System.out::println);

運行結果:

image

上面使用String中默認實現的接口自動完成排序。

 

⑧、sorted(Comparator<? super T> comparator):自定排序:按提供的比較符排序的流元素 。

        //自定排序
        students.stream().sorted((s1,s2)-> {
                    if(s1.getId()>s2.getId()){
                        return s1.getId().compareTo(s2.getId());
                    }else{
                        return -s1.getAge().compareTo(s2.getAge());
                    }
                }).forEach(System.out::println);

運行結果:

image

上面的代碼表示爲先按照id進行排序,若是id相同則按照年齡降序排序,你能夠將其餘id改成 1測試一下Open-mouthed smile

六、Stream終止操做

終止操做主要有如下方法:forEach、 forEachOrdered、anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、count、min、 max、 reduce、 collect、toArray、iterator。下面只介紹一下經常使用的方法。

注意:Stream流只要進行了終止操做後,就不能再次使用了,再次使用得從新建立流。

①、void forEach(Consumer<? super T> action):遍歷:接收Lambda表達式,遍歷流中的全部元素。

forEach上面已經用的夠多的了,這裏就不說了。

 

②、anyMatch/allMatch/noneMatch:匹配:返回值爲boolean,參數爲(Predicate<? super T> predicate)。

  • allMatch——檢查是否匹配全部元素
  • anyMatch——檢查是否至少匹配一個元素
  • noneMatch——檢查是否沒有匹配的元素
        boolean b = students.stream().allMatch((s) -> s.getAge() > 20);
        System.out.println("allMatch()"+"\t"+b);

        boolean b1 = students.stream().anyMatch((s) -> s.getAge() > 21);
        System.out.println("anyMatch()"+"\t"+b1);

        boolean b2 = students.stream().noneMatch((s) -> s.getAge() == 22);
        System.out.println("noMatch()"+"\t"+b2);

運行結果:

image

 

③、findFirst/findAny:查找:返回Optional<T>,無參數。

  • findFirst——返回第一個元素
  • findAny——返回當前流中的任意元素
        //先按id排好序
        Optional<Student> first = students.stream().sorted((s1, s2) -> s1.getId().compareTo(s2.getId())).findFirst();
        System.out.println("findFirst()"+"\t"+first.get());

        Optional<Student> any = students.stream().filter((s)->s.getAge()>19).findAny();
        System.out.println("findAny()"+"\t"+any.get());

運行結果:

image

 

④、long count():統計:返回流中元素的個數。

        //統計流中數量
        long count = students.stream().count();
        System.out.println("count()"+"\t"+count);

運行結果:

image

 

⑤、max/min:大小值:返回Optional<T>,無參數。

  • max——返回流中最大值
  • min——返回流中最小值
        //獲取年齡最大值
        Optional<Student> max = students.stream().max((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
        System.out.println("max()"+"\t"+max.get());
        //獲取年齡最小值
        Optional<Student> min = students.stream().min((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
        System.out.println("min()"+"\t"+min.get());

運行結果:

image

 

⑥、reduce:歸約:能夠將流中元素反覆結合在一塊兒,獲得一個值。

  • T reduce(T identity, BinaryOperator<T> accumulator)
  • Optional<T> reduce(BinaryOperator<T> accumulator)
        //累加0-10
        List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        Integer sum = list.stream().reduce(0, (x, y) -> x + y);
        //一、T reduce(T identity, BinaryOperator<T> accumulator);
        System.out.println("reduce1--"+sum);

        //二、Optional<T> reduce(BinaryOperator<T> accumulator);
        Optional<Integer> sum1 = list.stream().reduce(Integer::sum);
        System.out.println("reduce2--"+sum1.get());

        Optional<Integer> sum2 = list.stream().reduce(new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer1, Integer integer2) {
                return Integer.sum(integer1,integer2);
            }
        });
        System.out.println("reduce3--"+sum2.get());

運行結果:

image

備註:map和reduce的鏈接一般稱爲map-reduce模式,因Google用它來進行網絡搜索而出名。

七、Collectors的使用

Stream流與Collectors中的方法是很是好的搭檔,經過組合來以更簡單的方式來實現更增強大的功能,咱們利用Stream中的collect方法來實現。

collect:收集:將流轉換爲其餘形式。它接收一個 Collector接口的實現,用於給Stream中元素作彙總的方法。

Collector接口中方法得實現決定了如何對流執行收集操做(如收集到List,Set,Map)。可是Collectors實用類提供了不少靜態方法,能夠方便地建立常見得收集器實例。

因此下面逐一介紹Collectors中的方法:

①、Collectors.toList():將流轉換成List。

        /**
         * Collectors.toList():將流轉換成List
         */
        List<String> list = students.stream().map(student -> student.getName()).collect(Collectors.toList());
        System.out.println("toList----"+list);

 

②、Collectors.toSet():將流轉換爲Set。

        /**
         * Collectors.toSet():將流轉換成Set
         */
        Set<String> set = students.stream().map(student -> student.getName()).collect(Collectors.toSet());
        System.out.println("toSet----"+set);

 

③、Collectors.toCollection():將流轉換爲其餘類型的集合。

        /**
         * Collectors.toCollection():將流轉換爲其餘類型的集合
         */
        TreeSet<String> treeSet = students.stream().map(student -> student.getName()).collect(Collectors.toCollection(TreeSet::new));
        System.out.println("toCollection----"+treeSet);

 

④、Collectors.counting():元素個數。

        /**
         * Collectors.counting():元素個數
         */
        Long aLong = students.stream().collect(Collectors.counting());
        System.out.println("counting----"+aLong);

 

⑤、Collectors.averagingInt()、Collectors.averagingDouble()、Collectors.averagingLong():求平均數。

這三個方法均可以求平均數,不一樣之處在於傳入得參數類型不一樣,可是返回值都爲Double。

        /**
        *  Collectors.averagingInt()
        *  Collectors.averagingDouble()
        *  Collectors.averagingLong()
        *  求平均數
        */
        Double aDouble = students.stream().collect(Collectors.averagingInt(student -> student.getAge()));
        System.out.println("averagingInt()----"+aDouble);

        Double aDouble1 = students.stream().collect(Collectors.averagingDouble(student -> student.getAge()));
        System.out.println("averagingDouble()----"+aDouble1);

        Double aDouble2 = students.stream().collect(Collectors.averagingLong(student -> student.getAge()));
        System.out.println("averagingLong()----"+aDouble2);

 

⑥、Collectors.summingDouble()、Collectors.summingDouble()、Collectors.summingLong():求和。

這三個方法均可以求和,不一樣之處在於傳入得參數類型不一樣,返回值爲Integer, Double, Long。

        /**
         *  Collectors.summingInt()
         *  Collectors.summingDouble()
         *  Collectors.summingLong()
         *  求和
         */
        Integer integer = students.stream().collect(Collectors.summingInt(student -> student.getAge()));
        System.out.println("summingInt()----"+integer);

        Double aDouble3 = students.stream().collect(Collectors.summingDouble(student -> student.getAge()));
        System.out.println("summingDouble()----"+aDouble3);

        Long aLong1 = students.stream().collect(Collectors.summingLong(student -> student.getAge()));
        System.out.println("summingLong()----"+aLong1);

 

⑦、Collectors.maxBy():求最大值。

        /**
         * Collectors.maxBy():求最大值
         */
        Optional<Integer> integer1 = students.stream().map(student -> student.getId()).collect(Collectors.maxBy((x, y) -> Integer.compare(x, y)));
        System.out.println("maxBy()----"+integer1.get());

 

⑧、Collectors.minBy():求最小值。

        /**
         * Collectors.minBy():求最小值
         */
        Optional<Integer> integer2 = students.stream().map(student -> student.getId()).collect(Collectors.minBy((x, y) -> Integer.compare(x, y)));
        System.out.println("maxBy()----"+integer2.get());

 

 

⑨、Collectors.groupingBy():分組 ,返回一個map。

        /**
         * Collectors.groupingBy():分組 ,返回一個map
         */
        Map<Integer, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getId));
        System.out.println(listMap);

其中Collectors.groupingBy()還能夠實現多級分組,以下:

        /**
         * Collectors.groupingBy():多級分組 ,返回一個map
         */

        //先按name分組而後得出每組的學生數量,使用重載的groupingBy方法,第二個參數是分組後的操做
        Map<String, Long> stringLongMap = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
        System.out.println("groupingBy()多級分組----"+stringLongMap);

        //先按id分組而後再按年齡分組
        Map<Integer, Map<Integer, List<Student>>> integerMapMap = students.stream().collect(Collectors.groupingBy(Student::getId, Collectors.groupingBy(Student::getAge)));
        System.out.println("groupingBy()多級分組----"+integerMapMap);

        //遍歷
        Map<Integer,List<Student>> map = new HashMap<>();
        Iterator<Map.Entry<Integer, Map<Integer, List<Student>>>> iterator = integerMapMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<Integer, Map<Integer, List<Student>>> entry = iterator.next();
            System.out.println("key="+entry.getKey()+"----value="+entry.getValue());
        }

 

⑩、Collectors.partitioningBy():分區。按true和false分紅兩個區。

        /**
         * Collectors.partitioningBy():分區,分紅兩個區
         */
        Map<Boolean, List<Student>> listMap1 = students.stream().collect(Collectors.partitioningBy(student -> student.getAge() > 18));
        System.out.println(listMap1);

⑪、Collectors.joining():拼接。按特定字符將數據拼接起來。

        /**
         * Collectors.joining():拼接
         */
        String str = students.stream().map(student -> student.getName()).collect(Collectors.joining("---"));
        System.out.println(str);

八、結束語

Java8中的Stream提供的功能很是強大,用它們來操做集合會讓咱們用更少的代碼,更快的速度遍歷出集合。並且在java.util.stream包下還有個類Collectors,它和stream是好很是好的搭檔,經過組合來以更簡單的方式來實現更增強大的功能。雖然上面介紹的都是Stream的一些基本操做,可是隻要你們勤加練習就能夠靈活使用,很快的能夠運用到實際應用中。

相關文章
相關標籤/搜索