【Java8】Java8實(shí)戰(zhàn)之Stream

Java8實(shí)戰(zhàn)之Stream

前言

在前面一個(gè)小節(jié)中,我們已經(jīng)學(xué)習(xí)了行為參數(shù)化以及Lambda表達(dá)式,通過Lambda表達(dá)式,可以使得代碼更加簡潔,尤其是當(dāng)一個(gè)方法只需要使用一次的時(shí)候,然而,如果Java8中只有Lambda表達(dá)式的話,那還是不足以讓人感到興奮的,個(gè)人感覺,Java8中最有意思,也是最方便的功能,莫過于Stream

Stream初窺

Stream可以翻譯為流,實(shí)際上其操作也是,流操作是Java8中引入的新功能,提供了更加強(qiáng)大的數(shù)據(jù)迭代處理方式,通過流式寫法,提供了簡潔的語法,主要注意的是Stream需要配合Lambda表達(dá)式來使用,這更加體現(xiàn)了行為參數(shù)化的思想,Java8通過將既定的操作封裝好,同時(shí),將對(duì)應(yīng)的具體行為留給用戶,極大地提高了操作的效率。

Stream的出現(xiàn),可以說是用于替代傳統(tǒng)的容器操作的,在傳統(tǒng)的容器操作中,當(dāng)需要對(duì)容器中的某些元素進(jìn)行操作的時(shí)候,我們需要迭代容器,然后篩選出合適的對(duì)象,然后再將其存放到另外的容器中,從上面的描述中,可以看到,其中的很大一部分操作:迭代容器,篩選對(duì)象,重新存放基本都是固定的,而每次都進(jìn)行手動(dòng)操作,顯然是比較繁瑣的,Stream則提供了更加便捷的操作,只需要通過對(duì)應(yīng)的操作模式,然后給出對(duì)應(yīng)的條件,即可實(shí)現(xiàn)對(duì)既定元素的操作。

為了下面的操作方便,我們先構(gòu)造需要的元素

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

// 構(gòu)造數(shù)據(jù)
public static List<User> generateUserData() {
    Random random = new Random();
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 1000; i++) {
        users.add(new User(i, "user" + i, random.nextInt(100)));
    }
    return users;
}

假設(shè)現(xiàn)在有一個(gè)場景,我們需要從上面的列表中選取年齡大于20歲的對(duì)象,在傳統(tǒng)的容器操作中,一般我們會(huì)這樣操作

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

而在Java8中,我們可以用更加簡潔的方式來實(shí)現(xiàn)上面的操作

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

或者上面的案例看上去并沒有那么有優(yōu)勢,那么我們來看下下面的案例,根據(jù)年齡對(duì)用戶進(jìn)行分組,年齡在1-30為年輕人,31-60為中年人,60以上為老年人(例子例子,沒有實(shí)際價(jià)值)

傳統(tǒng)的操作,我們需要如下操作

public void groupUser() {
    List<User> users = generateUserData();
    Map<String, List<User>> userGroup = new HashMap<>();
    for (User user : users) {
        if (user.getAge() > 0 && user.getAge() <= 30) {
            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() <= 60) {
            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);
}

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

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

可以看到,代碼量以及自描述性的對(duì)比還是挺明顯的,Stream配合Lambda表達(dá)式,可以使得之前比較繁瑣的容器操作,變得非常簡單,而且,代碼本身的自解釋性也更強(qiáng)

Stream操作

在前面我們已經(jīng)見識(shí)到了Stream本身的特點(diǎn)--流式操作以及方便性,接下來我們來詳細(xì)學(xué)習(xí)Stream的用法。

Stream的操作可以分為兩種,一種是中間操作,例如前面的filter()操作,一種是結(jié)束操作,例如前面的collect()操作,每一個(gè)中間操作,都返回一個(gè)Stream,經(jīng)過本次處理之后的Stream,結(jié)束操作則產(chǎn)生終結(jié),其結(jié)果要么是數(shù)字,要么是字符串,要么是集合等等,總之就不再是Stream,也就是說,一個(gè)Stream可以有多個(gè)中間操作,但只能有一個(gè)結(jié)束操作

中間操作

比較常用的幾種中間操作列舉如下,更多的內(nèi)容參考API即可

  • filter(),過濾操作,入?yún)?code>Predicate<? super T> predicate
  • limit(),限制操作,入?yún)?code>long maxSize
  • skip(),跳過操作,入?yún)?code>long n
  • distinct(),去重操作,沒有入?yún)ⅲ讓邮褂玫氖?code>Set進(jìn)行去重
  • sorted(),排序操作,可以傳入自定義的比較器Comparator<? super T> comparator
  • peek(),檢查操作,用于調(diào)試操作,入?yún)?code>Consumer<? super T> action
  • map(),將Stream中的元素映射為其他元素,入?yún)?code>Function<? super T, ? extends R> mapper
    • mapToDouble(),將Stream轉(zhuǎn)為DoubleStream,避免裝箱機(jī)制所帶來的開銷
    • mapToLong(),將Stream轉(zhuǎn)為LongStream,避免裝箱機(jī)制所帶來的開銷
    • mapToInt(),將Stream轉(zhuǎn)為IntStream,避免裝箱機(jī)制所帶來的開銷
  • flatMap(),將多個(gè)Stream轉(zhuǎn)為一個(gè),注意與map()的區(qū)別,入?yún)?code>Function<? super T, ? extends Stream<? extends R>> mapper

結(jié)束操作

比較常用的幾個(gè)結(jié)束操作列舉如下,更多的內(nèi)容參考API即可

  • count(),統(tǒng)計(jì)元素個(gè)數(shù)
  • forEach(),對(duì)每個(gè)元素執(zhí)行操作,入?yún)?code>Consumer<? super T> action
  • findFirst(),獲取第一個(gè)元素
  • findAny(),獲取任意一個(gè)元素
  • anyMatch(),檢查元素是否至少有一個(gè)匹配,入?yún)?code>Predicate<? super T> predicate
  • allMatch(),檢查所有元素是否都匹配,入?yún)?code>Predicate<? super T> predicate
  • collect(),將所有內(nèi)容收集起來,入?yún)?code>Collector<? super T, A, R> collector,JDK中提供了眾多的Collector的實(shí)現(xiàn),所以,基本上不用自己實(shí)現(xiàn)
    • groupingBy(),將內(nèi)容進(jìn)行分組,有三個(gè)不同的版本
      • groupingBy(Function<? super T, ? extends K> classifier),僅能進(jìn)行一次分組
      • groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream),注意第二個(gè)參數(shù)可以是另一個(gè)Collector,也就是說,可以通過多次的復(fù)合,達(dá)到多次分組,或者分組后再進(jìn)行其他的操作
      • groupingBy(Function<? super T, ? extends K> classifier,Supplier<M> mapFactory, Collector<? super T, A, D> downstream),自己提供一個(gè)容器,而不是使用默認(rèn)的容器
    • counting(),等價(jià)于前面的Stream.count()
    • partitioningBy()精簡版的groupingBy(),僅能支持true、false兩種分組
    • joining(),字符串連接,需要注意,如果Stream的內(nèi)容本身不是字符串流,則需要先map()操作一下,將其轉(zhuǎn)為字符串流,可以指定分隔符,前綴,后綴
    • toList(),將結(jié)果合并為List
    • toSet(),將結(jié)果合并為Set
    • toMap(),將結(jié)果轉(zhuǎn)為Map
    • toConcurrentMap(),將結(jié)果轉(zhuǎn)為并發(fā)Map
  • reduce(),根據(jù)條件合并結(jié)果,可以說,上面的所有結(jié)束操作,基本上都可以通過reduce()來實(shí)現(xiàn),reduce有三個(gè)不同形式的參數(shù),當(dāng)JDK所提供的合并操作不滿足需求時(shí),可以通過reduce來實(shí)現(xiàn)自定義的合并操作
    • 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操作實(shí)例

為了更好地理解上面的內(nèi)容,我們通過幾個(gè)小例子來實(shí)際操作一下

    // 打印出年齡在30歲以上的所有用戶
    users.stream()
        .filter(user -> user.getAge() > 30)
        .forEach(System.out::println);
        // 如果換成 .count(),則是統(tǒng)計(jì)用戶的個(gè)數(shù)

    // 分組并且統(tǒng)計(jì)各個(gè)分組的人數(shù)
    Map<String, Long> collect = users.stream()
                .collect(groupingBy(user -> {
                    if (user.getAge() <= 30) {
                        return "young";
                    } else if (user.getAge() <= 60) {
                        return "middle";
                    } else {
                        return "old";
                    }
                }, counting()));
    
    // 分組并且去重
    Map<String, Set<User>> collect = users.stream()
                .collect(groupingBy(user -> {
                    if (user.getAge() <= 30) {
                        return "young";
                    } else if (user.getAge() <= 60) {
                        return "middle";
                    } else {
                        return "old";
                    }
                }, toSet()));

關(guān)于Stream的介紹,大致就到這里了,為了更好地掌握Stream,需要在實(shí)際使用中多加練習(xí),多加研究才是

總結(jié)

本小節(jié)主要學(xué)習(xí)了Stream的內(nèi)容,通過對(duì)比Stream與傳統(tǒng)的Collection操作,可以看出,通過Stream來操作容器,代碼將變得更加簡潔,而且,其可閱讀行也更強(qiáng),出錯(cuò)的概率也會(huì)更低,畢竟不用再自己關(guān)心迭代的過程,最后,通過幾個(gè)簡單的小例子,展示了Stream中兩種不同的操作,中間操作以及結(jié)束操作,當(dāng)然,關(guān)于Stream的更多內(nèi)容,還是需要在實(shí)際使用中不斷發(fā)現(xiàn),不斷研究,加油。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容