溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么從零開(kāi)始學(xué)習(xí)Java8 Stream

發(fā)布時(shí)間:2021-10-21 16:14:35 來(lái)源:億速云 閱讀:99 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“怎么從零開(kāi)始學(xué)習(xí)Java8 Stream”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“怎么從零開(kāi)始學(xué)習(xí)Java8 Stream”吧!

為何需要引入流

在我們平常的開(kāi)發(fā)中幾乎每天都會(huì)有到List、Map等集合API,若是問(wèn)Java什么API使用最多,我想也應(yīng)該是集合了。舉例:假如我有個(gè)集合List,里面元素有1,7,3,8,2,4,9,需要找出里面大于5的元素,具體實(shí)現(xiàn)代碼:

public List<integer> getGt5Data() {
    List<integer> data = Arrays.asList(1, 7, 3, 8, 2, 4, 9);
    List<integer> result = new ArrayList&lt;&gt;();
    for (Integer num : data) {
        if (num &gt; 5) {
            result.add(num);
        }
    }
    return result;
}

這個(gè)實(shí)現(xiàn)讓我們感覺(jué)到了集合的操作不是太完美,如果是數(shù)據(jù)庫(kù)的話,我們只需要簡(jiǎn)單的在where后面加一個(gè)條件大于5就可以得到我們想要的結(jié)果,為什么Java的集合就沒(méi)有這種API呢? 其次,如果我們遇到有大集合需要處理,為了提高性能,我們可能需要使用到多線程來(lái)處理,但是寫(xiě)并行程序的復(fù)雜度有提高了不少。

基于以上的問(wèn)題,所有Java8推出了Stream


Stream簡(jiǎn)介

Stream有哪些特點(diǎn):

  • 元素的序列:與集合一樣可以訪問(wèn)里面的元素,集合講的是數(shù)據(jù),而流講的是操作,比如:filter、map

  • 源: 流也需要又一個(gè)提供數(shù)據(jù)的源,順序和生成時(shí)的順序一致

  • 數(shù)據(jù)的操作:流支持類似于數(shù)據(jù)庫(kù)的操作,支持順序或者并行處理數(shù)據(jù);上面的例子用流來(lái)實(shí)現(xiàn)會(huì)更加的簡(jiǎn)潔

public List<integer> getGt5Data() {
    return Stream.of(1, 7, 3, 8, 2, 4, 9)
            .filter(num -&gt; num &gt; 5)
            .collect(toList());
}
  • 流水線操作:很多流的方法本身也會(huì)返回一個(gè)流,這樣可以把多個(gè)操作連接起來(lái),形成流水線操作

  • 內(nèi)部迭代:與以往的迭代不同,流使用的內(nèi)部迭代,用戶只需要專注于數(shù)據(jù)處理

  • 只能遍歷一次: 遍歷完成之后我們的流就已經(jīng)消費(fèi)完了,再次遍歷的話會(huì)拋出異常


使用Stream

Java8中的Stream定義了很多方法,基本可以把他們分為兩類:中間操作、終端操作;要使用一個(gè)流一般都需要三個(gè)操作:

  1. 定義一個(gè)數(shù)據(jù)源

  2. 定義中間操作形成流水線

  3. 定義終端操作,執(zhí)行流水線,生成計(jì)算結(jié)果

構(gòu)建流
  1. 使用Stream.of方法構(gòu)建一個(gè)流

Stream.of("silently","9527","silently9527.cn")
        .forEach(System.out::println);
  1. 使用數(shù)組構(gòu)建一個(gè)流

int[] nums = {3, 5, 2, 7, 8, 9};
Arrays.stream(nums).sorted().forEach(System.out::println);
  1. 通過(guò)文件構(gòu)建一個(gè)流 使用java.nio.file.Files.lines方法可以輕松構(gòu)建一個(gè)流對(duì)象

Files.lines(Paths.get("/Users/huaan9527/Desktop/data.txt"))
                .forEach(System.out::println);
中間操作

中間操作會(huì)返回另外一個(gè)流,這樣可以讓多個(gè)操作連接起來(lái)形成一個(gè)流水線的操作,只要不觸發(fā)終端操作,那么這個(gè)中間操作都不會(huì)實(shí)際執(zhí)行。

filter

該操作接受一個(gè)返回boolean的函數(shù),當(dāng)返回false的元素將會(huì)被排除掉

舉例:假如我們100個(gè)客戶,需要篩選出年齡大于20歲的客戶

List<customer> matchCustomers = allCustomers.stream()
                .filter(customer -&gt; customer.getAge()&gt;20)
                .collect(toList());
distinct

該操作將會(huì)排除掉重復(fù)的元素

List<integer> data = Stream.of(1, 7, 3, 8, 2, 4, 9, 7, 9)
        .filter(num -&gt; num &gt; 5)
        .distinct()
        .collect(toList());
limit

該方法限制流只返回指定個(gè)數(shù)的元素

List<integer> data = Stream.of(1, 7, 3, 8, 2, 4, 9, 7, 9)
        .filter(num -&gt; num &gt; 5)
        .limit(2)
        .collect(toList());
skip

扔掉前指定個(gè)數(shù)的元素;配合limit使用可以達(dá)到翻頁(yè)的效果

List<integer> data = Stream.of(1, 7, 3, 8, 2, 4, 9, 7, 9)
        .filter(num -&gt; num &gt; 5)
        .skip(1)
        .limit(2)
        .collect(toList());
map

該方法提供一個(gè)函數(shù),流中的每個(gè)元素都會(huì)應(yīng)用到這個(gè)函數(shù)上,返回的結(jié)果將形成新類型的流繼續(xù)后續(xù)操作。 舉例:假如我們100個(gè)客戶,需要篩選出年齡大于20歲的客戶,打印出他們的名字

allCustomers.stream()
            .filter(customer -&gt; customer.getAge() &gt; 20)
            .map(Customer::getName)
            .forEach(System.out::println);

在調(diào)用map之前流的類型是Stream<customer>,執(zhí)行完map之后的類型是Stream<string>

flatMap

假如我們需要把客戶的名字中的每個(gè)字符打印出來(lái),代碼如下:

List<customer> allCustomers = Arrays.asList(new Customer("silently9527", 30));
allCustomers.stream()
        .filter(customer -&gt; customer.getAge() &gt; 20)
        .map(customer -&gt; customer.getName().split(""))
        .forEach(System.out::println);

執(zhí)行本次結(jié)果,你會(huì)發(fā)現(xiàn)沒(méi)有達(dá)到期望的結(jié)果,打印的結(jié)果

[Ljava.lang.String;@38cccef

這是因?yàn)檎{(diào)用map之后返回的流類型是Stream<string[]>,所有forEach的輸入就是String[];這時(shí)候我們需要使用flatMap把String[]中的每個(gè)元素都轉(zhuǎn)換成一個(gè)流,然后在把所有的流連接成一個(gè)流,修改后的代碼如下

List<customer> allCustomers = Arrays.asList(new Customer("silently9527", 30));
allCustomers.stream()
        .filter(customer -&gt; customer.getAge() &gt; 20)
        .map(customer -&gt; customer.getName().split(""))
        .flatMap(Arrays::stream)
        .forEach(System.out::println);

執(zhí)行結(jié)果:

怎么從零開(kāi)始學(xué)習(xí)Java8 Stream

sorted

對(duì)所有的元素進(jìn)行排序

List<integer> numbers = Arrays.asList(1, 7, 3, 8, 2, 4, 9);
numbers.stream().sorted(Integer::compareTo).forEach(System.out::println);
終端操作

終端操作會(huì)執(zhí)行所有的中間操作生成執(zhí)行的結(jié)果,執(zhí)行的結(jié)果不在是一個(gè)流。

anyMatch

如果流中有一個(gè)元素滿足條件將返回true

if (allCustomers.stream().anyMatch(customer -&gt; "silently9527".equals(customer.getName()))) {
    System.out.println("存在用戶silently9527");
}
allMatch

確保流中所有的元素都能滿足

if (allCustomers.stream().allMatch(customer -&gt; customer.getAge() &gt; 20)) {
    System.out.println("所有用戶年齡都大于20");
}
noneMatch

與allMatch操作相反,確保流中所有的元素都不滿足

if (allCustomers.stream().noneMatch(customer -&gt; customer.getAge() &lt; 20)) {
    System.out.println("所有用戶年齡都大于20");
}
findAny

返回流中的任意一個(gè)元素,比如返回大于20歲的任意一個(gè)客戶

Optional<customer> optional = allCustomers.stream()
        .filter(customer -&gt; customer.getAge() &gt; 20)
        .findAny();
findFirst

返回流中的第一個(gè)元素

Optional<customer> optional = allCustomers.stream()
        .filter(customer -&gt; customer.getAge() &gt; 20)
        .findFirst();
reduce

接受兩個(gè)參數(shù):一個(gè)初始值,一個(gè)BinaryOperator<t> accumulator將兩個(gè)元素合并成一個(gè)新的值 比如我們對(duì)一個(gè)數(shù)字list累加

List<integer> numbers = Arrays.asList(1, 7, 3, 8, 2, 4, 9);
Integer sum = numbers.stream().reduce(0, (a, b) -&gt; a + b);

上面的代碼,我們可以簡(jiǎn)寫(xiě)

Integer reduce = numbers.stream().reduce(0, Integer::sum);

找出流中的最大值、最小值 min、max

numbers.stream().reduce(Integer::max)
numbers.stream().reduce(Integer::min)
count

統(tǒng)計(jì)流中元素的個(gè)數(shù)

numbers.stream().count()

數(shù)據(jù)收集器collect

在Java8中已經(jīng)預(yù)定義了很多收集器,我們可以直接使用,所有的收集器都定義在了Collectors中,基本上可以把這些方法分為三類:

  • 將元素歸約和匯總成一個(gè)值

  • 分組

  • 分區(qū)

歸約和匯總

先看下我們之前求最大值和最小值采用收集器如何實(shí)現(xiàn)

  1. 找出年齡最大和最小的客戶

Optional<customer> minAgeCustomer = allCustomers.stream().collect(minBy(Comparator.comparing(Customer::getAge)));
Optional<customer> maxAgeCustomer = allCustomers.stream().collect(maxBy(Comparator.comparing(Customer::getAge)));
  1. 求取年齡的平均值

Double avgAge = allCustomers.stream().collect(averagingInt(Customer::getAge));
  1. 進(jìn)行字符串的連接

把客戶所有人的名字連接成一個(gè)字符串用逗號(hào)分隔

allCustomers.stream().map(Customer::getName).collect(joining(","));
分組

在數(shù)據(jù)庫(kù)的操作中,我們可以輕松的實(shí)現(xiàn)通過(guò)一個(gè)屬性或者多個(gè)屬性進(jìn)行數(shù)據(jù)分組,接下來(lái)我們看看Java8如何來(lái)實(shí)現(xiàn)這個(gè)功能。

  1. 根據(jù)客戶的年齡進(jìn)行分組

Map<integer, list<customer>&gt; groupByAge = allCustomers.stream().collect(groupingBy(Customer::getAge));

Map的key就是分組的值年齡,List<customer>就是相同年齡的用戶

  1. 我們需要先按照用戶的地區(qū)分組,在按年齡分組

Map<string, map<integer, list<customer>&gt;&gt; groups = allCustomers.stream()
                .collect(groupingBy(Customer::getArea, groupingBy(Customer::getAge)));

在相對(duì)于普通的分組,這里多傳了第二個(gè)參數(shù)又是一個(gè)groupingBy;理論上我們可以通過(guò)這個(gè)方式擴(kuò)展到n層分組

  1. 分組后再統(tǒng)計(jì)數(shù)量

Map<string, long> groupByCounting = allCustomers.stream()
            .collect(groupingBy(Customer::getArea, counting()));
  1. 以用戶所在地區(qū)分組后找出年齡最大的用戶

Map<string, optional<customer>&gt; optionalMap = allCustomers.stream()
                .collect(groupingBy(Customer::getArea, maxBy(Comparator.comparing(Customer::getAge))));

這時(shí)候返回的Map中的value被Optional包裹,如果我們需要去掉Optional,可以使用collectingAndThen

Map<string, customer> customerMap = allCustomers.stream()
        .collect(groupingBy(Customer::getArea,
                collectingAndThen(maxBy(Comparator.comparing(Customer::getAge)), Optional::get)
        ));

感謝各位的閱讀,以上就是“怎么從零開(kāi)始學(xué)習(xí)Java8 Stream”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)怎么從零開(kāi)始學(xué)習(xí)Java8 Stream這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI