天天看點

【Java8】Java8實戰之StreamJava8實戰之Stream

Java8實戰之Stream

前言

在前面一個小節中,我們已經學習了行為參數化以及Lambda表達式,通過Lambda表達式,可以使得代碼更加簡潔,尤其是當一個方法隻需要使用一次的時候,然而,如果Java8中隻有Lambda表達式的話,那還是不足以讓人感到興奮的,個人感覺,Java8中最有意思,也是最友善的功能,莫過于

Stream

Stream初窺

Stream

可以翻譯為流,實際上其操作也是,流操作是Java8中引入的新功能,提供了更加強大的資料疊代處理方式,通過流式寫法,提供了簡潔的文法,主要注意的是

Stream

需要配合Lambda表達式來使用,這更加展現了行為參數化的思想,Java8通過将既定的操作封裝好,同時,将對應的具體行為留給使用者,極大地提高了操作的效率。

Stream

的出現,可以說是用于替代傳統的容器操作的,在傳統的容器操作中,當需要對容器中的某些元素進行操作的時候,我們需要疊代容器,然後篩選出合适的對象,然後再将其存放到另外的容器中,從上面的描述中,可以看到,其中的很大一部分操作:疊代容器,篩選對象,重新存放基本都是固定的,而每次都進行手動操作,顯然是比較繁瑣的,

Stream

則提供了更加便捷的操作,隻需要通過對應的操作模式,然後給出對應的條件,即可實作對既定元素的操作。

為了下面的操作友善,我們先構造需要的元素

// User對象
class User {
    private Integer id;
    private String name;
    private Integer age;
    // 省略set,get,toString方法
}

// 構造資料
public static List<User> generateUserData() {
    Random random = new Random();
    List<User> users = new ArrayList<>();
    for (int i = ; i < ; i++) {
        users.add(new User(i, "user" + i, random.nextInt()));
    }
    return users;
}
           

假設現在有一個場景,我們需要從上面的清單中選取年齡大于20歲的對象,在傳統的容器操作中,一般我們會這樣操作

public List<User> getUserOlderThan20() {
    List<User> users = generateUserData();
    List<User> result = new ArrayList<>();
    for (User user : users) {
        if (user.getAge() >  ) {
            result.add(user);
        }
    }
    return result;
}
           

而在Java8中,我們可以用更加簡潔的方式來實作上面的操作

public List<User> getUserOlderThan20() {
    List<User> users = generateUserData();
    List<User> result = users.stream()
            .filter(user -> user.getAge() > )
            .collect(Collectors.toList());
    return result;
}
           

或者上面的案例看上去并沒有那麼有優勢,那麼我們來看下下面的案例,根據年齡對使用者進行分組,年齡在1-30為年輕人,31-60為中年人,60以上為老年人(例子例子,沒有實際價值)

傳統的操作,我們需要如下操作

public void groupUser() {
    List<User> users = generateUserData();
    Map<String, List<User>> userGroup = new HashMap<>();
    for (User user : users) {
        if (user.getAge() >  && user.getAge() <= ) {
            List<User> young = userGroup.get("young");
            if (young == null) {
                young = new ArrayList<>();
                userGroup.put("young", young);
            }
            userGroup.get("young").add(user);
        }else if (user.getAge() <= ) {
            List<User> middle = userGroup.get("middle");
            if (middle == null) {
                middle = new ArrayList<>();
                userGroup.put("middle", middle);
            }
            userGroup.get("middle").add(user);
        }else {
            List<User> old = userGroup.get("old");
            if (old == null) {
                old = new ArrayList<>();
                userGroup.put("old", old);
            }
            userGroup.get("old").add(user);
        }
    }
    System.out.println(userGroup);
}
           

可以看到,上面的操作還是挺繁瑣的,而且比較容易出錯,而在Java8中,我們則可以采用如下操作

public void testStream() {
    List<User> users = generateUserData();
    Map<String, List<User>> result = users.stream()
            .collect(Collectors.groupingBy(
                            user -> {
                                if (user.getAge() >  && user.getAge() <= ) {
                                    return "young";
                                } else if (user.getAge() <= ) {
                                    return "middle";
                                } else {
                                    return "old";
                                }}
                            ));
    System.out.println(result);
}
           

可以看到,代碼量以及自描述性的對比還是挺明顯的,

Stream

配合

Lambda

表達式,可以使得之前比較繁瑣的容器操作,變得非常簡單,而且,代碼本身的自解釋性也更強

Stream操作

在前面我們已經見識到了

Stream

本身的特點–流式操作以及友善性,接下來我們來詳細學習

Stream

的用法。

Stream

的操作可以分為兩種,一種是中間操作,例如前面的

filter()

操作,一種是結束操作,例如前面的

collect()

操作,每一個中間操作,都傳回一個

Stream

,經過本次處理之後的

Stream

,結束操作則産生終結,其結果要麼是數字,要麼是字元串,要麼是集合等等,總之就不再是

Stream

,也就是說,一個

Stream

可以有多個中間操作,但隻能有一個結束操作

中間操作

比較常用的幾種中間操作列舉如下,更多的内容參考API即可

  • filter()

    ,過濾操作,入參為

    Predicate<? super T> predicate

  • limit()

    ,限制操作,入參為

    long maxSize

  • skip()

    ,跳過操作,入參為

    long n

  • distinct()

    ,去重操作,沒有入參,底層使用的是

    Set

    進行去重
  • sorted()

    ,排序操作,可以傳入自定義的比較器

    Comparator<? super T> comparator

  • peek()

    ,檢查操作,用于調試操作,入參

    Consumer<? super T> action

  • map()

    ,将Stream中的元素映射為其他元素,入參

    Function<? super T, ? extends R> mapper

    • mapToDouble()

      ,将Stream轉為

      DoubleStream

      ,避免裝箱機制所帶來的開銷
    • mapToLong()

      ,将Stream轉為

      LongStream

      ,避免裝箱機制所帶來的開銷
    • mapToInt()

      ,将Stream轉為

      IntStream

      ,避免裝箱機制所帶來的開銷
  • flatMap()

    ,将多個Stream轉為一個,注意與

    map()

    的差別,入參

    Function<? super T, ? extends Stream<? extends R>> mapper

結束操作

比較常用的幾個結束操作列舉如下,更多的内容參考API即可

  • count()

    ,統計元素個數
  • forEach()

    ,對每個元素執行操作,入參

    Consumer<? super T> action

  • findFirst()

    ,擷取第一個元素
  • findAny()

    ,擷取任意一個元素
  • anyMatch()

    ,檢查元素是否至少有一個比對,入參

    Predicate<? super T> predicate

  • allMatch()

    ,檢查所有元素是否都比對,入參

    Predicate<? super T> predicate

  • collect()

    ,将所有内容收集起來,入參

    Collector<? super T, A, R> collector

    ,JDK中提供了衆多的

    Collector

    的實作,是以,基本上不用自己實作
    • groupingBy()

      ,将内容進行分組,有三個不同的版本
      • groupingBy(Function<? super T, ? extends K> classifier)

        ,僅能進行一次分組
      • groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)

        ,注意第二個參數可以是另一個

        Collector

        ,也就是說,可以通過多次的複合,達到多次分組,或者分組後再進行其他的操作
      • groupingBy(Function<? super T, ? extends K> classifier,Supplier<M> mapFactory, Collector<? super T, A, D> downstream)

        ,自己提供一個容器,而不是使用預設的容器
    • counting()

      ,等價于前面的

      Stream.count()

    • partitioningBy()

      精簡版的

      groupingBy()

      ,僅能支援

      true

      false

      兩種分組
    • joining()

      ,字元串連接配接,需要注意,如果Stream的内容本身不是字元串流,則需要先

      map()

      操作一下,将其轉為字元串流,可以指定分隔符,字首,字尾
    • toList()

      ,将結果合并為List
    • toSet()

      ,将結果合并為Set
    • toMap()

      ,将結果轉為Map
    • toConcurrentMap()

      ,将結果轉為并發Map
  • reduce()

    ,根據條件合并結果,可以說,上面的所有結束操作,基本上都可以通過

    reduce()

    來實作,

    reduce

    有三個不同形式的參數,當JDK所提供的合并操作不滿足需求時,可以通過

    reduce

    來實作自定義的合并操作
    • T reduce(T identity, BinaryOperator<T> accumulator)

    • Optional<T> reduce(BinaryOperator<T> accumulator)

    • <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)

Stream操作執行個體

為了更好地了解上面的内容,我們通過幾個小例子來實際操作一下

// 列印出年齡在30歲以上的所有使用者
    users.stream()
        .filter(user -> user.getAge() > )
        .forEach(System.out::println);
        // 如果換成 .count(),則是統計使用者的個數

    // 分組并且統計各個分組的人數
    Map<String, Long> collect = users.stream()
                .collect(groupingBy(user -> {
                    if (user.getAge() <= ) {
                        return "young";
                    } else if (user.getAge() <= ) {
                        return "middle";
                    } else {
                        return "old";
                    }
                }, counting()));

    // 分組并且去重
    Map<String, Set<User>> collect = users.stream()
                .collect(groupingBy(user -> {
                    if (user.getAge() <= ) {
                        return "young";
                    } else if (user.getAge() <= ) {
                        return "middle";
                    } else {
                        return "old";
                    }
                }, toSet()));
           

關于Stream的介紹,大緻就到這裡了,為了更好地掌握Stream,需要在實際使用中多加練習,多加研究才是

總結

本小節主要學習了Stream的内容,通過對比Stream與傳統的Collection操作,可以看出,通過Stream來操作容器,代碼将變得更加簡潔,而且,其可閱讀行也更強,出錯的機率也會更低,畢竟不用再自己關心疊代的過程,最後,通過幾個簡單的小例子,展示了Stream中兩種不同的操作,中間操作以及結束操作,當然,關于Stream的更多内容,還是需要在實際使用中不斷發現,不斷研究,加油。