樂(lè)字節(jié)-Java8核心特性實(shí)戰(zhàn)之Stream(流)

說(shuō)起流,我們會(huì)想起手機(jī) ,電腦組裝流水線,物流倉(cāng)庫(kù)商品包裝流水線等等。如果把手機(jī) ,電腦,包裹看做最終結(jié)果的話,那么加工商品前的各種零部件就可以看做數(shù)據(jù)源,而中間一系列的加工作業(yè)操作,就可以看做流的處理。

一、流的概念

Java Se中對(duì)于流的操作有輸入輸出IO流,而Java8中引入的Stream 屬于Java API中的一個(gè)新成員,它允許你以聲明性方式處理數(shù)據(jù)集合,Stream 使用一種類(lèi)似 SQL 語(yǔ)句從數(shù)據(jù)庫(kù)查詢數(shù)據(jù)的直觀方式來(lái)提供一種對(duì) Java 集合運(yùn)算和表達(dá)的高階抽象。 注意這里的流操作可以看做是對(duì)集合數(shù)據(jù)的處理。

簡(jiǎn)單來(lái)說(shuō),流是一種數(shù)據(jù)渠道,用于操作數(shù)據(jù)源(集合、數(shù)組、文件等)所生產(chǎn)的元素序列。

源-流會(huì)使用一個(gè)提供數(shù)據(jù)的源,如集合、數(shù)組或輸入|輸出資源。

從有序集生成流時(shí)會(huì)保留原有的順序。由列表生成的流,其元素順序與列表一致

元素序列-就像集合一樣,流也提供了一個(gè)接口,可以訪問(wèn)特定元素類(lèi)型的一組有序值。

數(shù)據(jù)處理操作-流的數(shù)據(jù)處理功能支持類(lèi)似于數(shù)據(jù)庫(kù)的操作(數(shù)據(jù)篩選、過(guò)濾、排序等操作)。

流水線-多個(gè)流操作本身會(huì)返回一個(gè)流,多個(gè)操作就可以鏈接起來(lái),成為數(shù)據(jù)處理的一道流水線。

二、流 & 集合

計(jì)算的時(shí)機(jī)

集合中數(shù)據(jù)都是計(jì)算完畢的數(shù)據(jù),例如從數(shù)據(jù)庫(kù)中查詢用戶記錄 按用戶id 查詢 降序排列 然后通過(guò)list 接收用戶記錄,數(shù)據(jù)的計(jì)算已在放入集合前完成。

流中數(shù)據(jù)按需計(jì)算,按照使用者的需要計(jì)算數(shù)據(jù),例如通過(guò)搜索引擎進(jìn)行搜索,搜索出來(lái)的條目并不是全部呈現(xiàn)出來(lái)的,而且先顯示最符合的前 10 條或者前 20 條,只有在點(diǎn)擊 “下一頁(yè)” 的時(shí)候,才會(huì)再輸出新的 10 條。流的計(jì)算也是這樣,當(dāng)用戶需要對(duì)應(yīng)數(shù)據(jù)時(shí),Stream 才會(huì)對(duì)其進(jìn)行計(jì)算處理。

外部迭代與內(nèi)部迭代

把集合比作一個(gè)工廠的倉(cāng)庫(kù)的話,一開(kāi)始工廠硬件比較落后,要對(duì)貨物作什么修改,此時(shí)工人親自走進(jìn)倉(cāng)庫(kù)對(duì)貨物進(jìn)行處理,有時(shí)候還要將處理后的貨物轉(zhuǎn)運(yùn)到另一個(gè)倉(cāng)庫(kù)中。此時(shí)對(duì)于開(kāi)發(fā)者來(lái)說(shuō)需要親自去做迭代,一個(gè)個(gè)地找到需要的貨物,并進(jìn)行處理,這叫做外部迭代。

當(dāng)工廠發(fā)展起來(lái)后,配備了流水線作業(yè),工廠只要根據(jù)需求設(shè)計(jì)出相應(yīng)的流水線,然后工人只要把貨物放到流水線上,就可以等著接收成果了,而且流水線還可以根據(jù)要求直接把貨物輸送到相應(yīng)的倉(cāng)庫(kù)。這就叫做內(nèi)部迭代,流水線已經(jīng)幫你把迭代給完成了,你只需要說(shuō)要干什么就可以了(即設(shè)計(jì)出合理的流水線)。相當(dāng)于 Java8 引入的Stream 對(duì)數(shù)據(jù)的處理實(shí)現(xiàn)了”自動(dòng)化”操作。

三、流操作過(guò)程

整個(gè)流操作就是一條流水線,將元素放在流水線上一個(gè)個(gè)地進(jìn)行處理。需要注意的是:很多流操作本身就會(huì)返回一個(gè)流,所以多個(gè)操作可以直接連接起來(lái), 如下圖這樣,操作可以進(jìn)行鏈?zhǔn)秸{(diào)用,并且并行流還可以實(shí)現(xiàn)數(shù)據(jù)流并行處理操作。

總的來(lái)說(shuō),流操作過(guò)程分為三個(gè)階段:

創(chuàng)建

借助數(shù)據(jù)源創(chuàng)建流對(duì)象

中間處理

篩選、切片、映射、排序等中間操作

終止流

匹配、匯總、分組等終止操作

四、流的創(chuàng)建

對(duì)流操作首先要?jiǎng)?chuàng)建對(duì)應(yīng)的流,流的創(chuàng)建集中形式如下:

4.1 集合創(chuàng)建流

在 Java 8 中, 集合接口有兩個(gè)方法來(lái)生成流:

stream()?? 為集合創(chuàng)建串行流。

parallelStream()?? 為集合創(chuàng)建并行流。

示例代碼如下:

publicstaticvoidmain(String[]args){/**? ? ? ? * 定義集合l1 并為集合創(chuàng)建串行流? ? ? ? */List<String>l1=Arrays.asList("周星馳","周杰倫","周星星","周潤(rùn)發(fā)");// 返回串行流l1.stream();// 返回并行流l1.parallelStream();}

上述操作得到的流是通過(guò)原始數(shù)據(jù)轉(zhuǎn)換過(guò)來(lái)的流,除了這種流創(chuàng)建的基本操作外,對(duì)于流的創(chuàng)建還有以下幾種方式。

4.2 值創(chuàng)建流

Stream.of(T...) : Stream.of("aa", "bb") 生成流

//值創(chuàng)建流 生成一個(gè)字符串流Stream<String>stream=Stream.of("java8","Spring","SpringCloud");stream.forEach(System.out::println);

4.3 數(shù)組創(chuàng)建流

根據(jù)參數(shù)的數(shù)組類(lèi)型創(chuàng)建對(duì)應(yīng)的流。

Arrays.stream(T[ ])

Arrays.stream(int[ ])

Arrays.stream(double[ ])

Arrays.stream(long[ ])

/** * 這里以int 為例? long double 不再舉例 */Streamstream=Arrays.stream(Arrays.asList(10,20,30,40).toArray());// 根據(jù)數(shù)組索引范圍創(chuàng)建指定Streamstream=Arrays.stream(Arrays.asList(10,20,30,40).toArray(),0,2);

4.4 文件生成流

stream=Files.lines(Paths.get("C:\\java\\jdbc.properties"));System.out.println(stream.collect(Collectors.toList()));// 指定字符集編碼stream=Files.lines(Paths.get("C:\\java\\jdbc.properties"),Charset.forName("utf-8"));System.out.println(stream.collect(Collectors.toList()));

4.5 函數(shù)生成流

兩個(gè)方法:

iterate : 依次對(duì)每個(gè)新生成的值應(yīng)用函數(shù)

generate :接受一個(gè)函數(shù),生成一個(gè)新的值

// 重100 開(kāi)始 生成偶數(shù)流Stream.iterate(100,n->n+2);// 產(chǎn)生1-100 隨機(jī)數(shù)Stream.generate(()->(int)(Math.random()*100+1));

五、流中間操作

流的中間操作分為三大類(lèi):篩選切片、映射、排序。

篩選切片:類(lèi)似sql 中where 條件判斷的意思,對(duì)元素進(jìn)行篩選操作

映射:對(duì)元素結(jié)果進(jìn)行轉(zhuǎn)換 ,優(yōu)點(diǎn)類(lèi)似select 字段意思或者對(duì)元素內(nèi)容進(jìn)行轉(zhuǎn)換處理

排序:比較好理解 ,常用sql 中按字段升序 降序操作

流中間操作數(shù)據(jù)準(zhǔn)備(這里以訂單數(shù)據(jù)處理為例)

@DatapublicclassOrder{// 訂單idprivateIntegerid;// 訂單用戶idprivateIntegeruserId;// 訂單編號(hào)privateStringorderNo;// 訂單日期privateDateorderDate;// 收貨地址privateStringaddress;// 創(chuàng)建時(shí)間privateDatecreateDate;// 更新時(shí)間privateDateupdateDate;// 訂單狀態(tài)? 0-未支付? 1-已支付? 2-待發(fā)貨? 3-已發(fā)貨? 4-已接收? 5-已完成privateIntegerstatus;// 是否有效? 1-有效訂單? 0-無(wú)效訂單privateIntegerisValid;//訂單總金額privateDoubletotal;}?Orderorder01=newOrder(1,10,"20190301",newDate(),"上海市-浦東區(qū)",newDate(),newDate(),4,1,100.0);Orderorder02=newOrder(2,30,"20190302",newDate(),"北京市四惠區(qū)",newDate(),newDate(),1,1,2000.0);Orderorder03=newOrder(3,20,"20190303",newDate(),"北京市-朝陽(yáng)區(qū)",newDate(),newDate(),4,1,500.0);Orderorder04=newOrder(4,40,"20190304",newDate(),"北京市-大興區(qū)",newDate(),newDate(),4,1,256.0);Orderorder05=newOrder(5,40,"20190304",newDate(),"上海市-松江區(qū)",newDate(),newDate(),4,1,1000.0);ordersList=Arrays.asList(order01,order02,order03,order04,order05);

六、篩選&切片

篩選有效訂單

// 過(guò)濾有效訂單ordersList.stream().filter((order)->order.getIsValid()==1).forEach(System.out::println);

篩選有效訂單 取第一頁(yè)數(shù)據(jù)(每頁(yè)2條記錄)

// 過(guò)濾有效訂單 取第一頁(yè)數(shù)據(jù)(每頁(yè)2條記錄) ordersList.stream().filter((order)->order.getIsValid()==1).limit(2).forEach(System.out::println);

篩選訂單集合有效訂單 取最后一條記錄

// 過(guò)濾訂單集合有效訂單 取最后一條記錄ordersList.stream().filter((order)->order.getIsValid()==1).skip(ordersList.size()-2)// 跳過(guò)前ordersList.size()-2 記錄.forEach(System.out::println);

篩選有效訂單 取第3頁(yè)數(shù)據(jù)(每頁(yè)2條記錄)

// 過(guò)濾有效訂單 取第3頁(yè)數(shù)據(jù)(每頁(yè)2條記錄) 并打印到控制臺(tái)ordersList.stream().filter((order)->order.getIsValid()==1).skip((3-1)*2).limit(2).forEach(System.out::println);

篩選無(wú)效訂單去除重復(fù)訂單號(hào)記錄

// 過(guò)濾無(wú)效訂單 去除重復(fù)訂單號(hào)記錄? 重寫(xiě)Order equals 與 hashCode 方法ordersList.stream().filter((order)->order.getIsValid()==0).distinct().forEach(System.out::println);

七、映射

過(guò)濾有效訂單,獲取所有訂單編號(hào)

//過(guò)濾有效訂單,獲取所有訂單編號(hào)ordersList.stream().filter((order)->order.getIsValid()==1).map((order)->order.getOrderNo()).forEach(System.out::println);

過(guò)濾有效訂單 ,并分離每個(gè)訂單下收貨地址市區(qū)信息

ordersList.stream().map(o->o.getAddress().split("-")).flatMap(Arrays::stream).forEach(System.out::println);

八、排序

過(guò)濾有效訂單 根據(jù)用戶id 進(jìn)行排序

//過(guò)濾有效訂單 根據(jù)用戶id 進(jìn)行排序ordersList.stream().filter((order)->order.getIsValid()==1).sorted((o1,o2)->o1.getUserId()-o2.getUserId()).forEach(System.out::println);?//或者等價(jià)寫(xiě)法 ordersList.stream().filter((order)->order.getIsValid()==1).sorted(Comparator.comparingInt(Order::getUserId)).forEach(System.out::println);

過(guò)濾有效訂單 ,根據(jù)訂單狀態(tài)排序 如果訂單狀態(tài)相同根據(jù)訂單創(chuàng)建時(shí)間排序

//過(guò)濾有效訂單? 如果訂單狀態(tài)相同 根據(jù)訂單創(chuàng)建時(shí)間排序 反之根據(jù)訂單狀態(tài)排序ordersList.stream().filter((order)->order.getIsValid()==1).sorted((o1,o2)->{if(o1.getStatus().equals(o2.getStatus())){returno1.getCreateDate().compareTo(o2.getCreateDate());}else{returno1.getStatus().compareTo(o2.getStatus());}}).forEach(System.out::println);??// 等價(jià)形式ordersList.stream().filter((order)->order.getIsValid()==1).sorted(Comparator.comparing(Order::getCreateDate).thenComparing(Comparator.comparing(Order::getStatus))).forEach(System.out::println);

九、流的終止操作

終止操作會(huì)從流的流水線生成結(jié)果。其結(jié)果是任何不是流的值,比如常見(jiàn)的List、 Integer,甚 至void等結(jié)果。對(duì)于流的終止操作,分為以下三類(lèi):

十、查找與匹配

篩選有效訂單 匹配是否全部為已支付訂單

// 篩選有效訂單 匹配是否全部為已支付訂單System.out.println("allMatch匹配結(jié)果:"+ordersList.stream().filter((order)->order.getIsValid()==1).allMatch((o)->o.getStatus()!=0));

篩選有效訂單 匹配是否存在未支付訂單

// 篩選有效訂單 匹配是否存在未支付訂單System.out.println("anyMatch匹配結(jié)果:"+ordersList.stream().filter((order)->order.getIsValid()==1).anyMatch((o)->o.getStatus()==0));

篩選有效訂單 全部未完成訂單

// 篩選有效訂單 全部未完成訂單System.out.println("noneMatch匹配結(jié)果:"+ordersList.stream().filter((order)->order.getIsValid()==1).noneMatch((o)->o.getStatus()==5));

篩選有效訂單 返回第一條訂單

// 篩選有效訂單 返回第一條訂單System.out.println("findAny匹配結(jié)果:"+ordersList.stream().filter((order)->order.getIsValid()==1).findAny().get());

篩選所有有效訂單 返回訂單總數(shù)

//? 篩選所有有效訂單 返回訂單總數(shù)System.out.println("count結(jié)果:"+ordersList.stream().filter((order)->order.getIsValid()==1).count());

篩選有效訂單 返回金額最大訂單金額

// 篩選有效訂單 返回金額最大訂單金額System.out.println("訂單金額最大值:"+ordersList.stream().filter((order)->order.getIsValid()==1).map(Order::getTotal).max(Double::compare).get());

篩選有效訂單 返回金額最小訂單金額

// 篩選有效訂單 返回金額最小訂單金額System.out.println("訂單金額最小值:"+ordersList.stream().filter((order)->order.getIsValid()==1).map(Order::getTotal).min(Double::compare).get());

十一、歸約&收集

11.1 歸約

將流中元素反復(fù)結(jié)合起來(lái),得到一個(gè)值的操作

計(jì)算有效訂單總金額

// 計(jì)算有效訂單總金額System.out.println("有效訂單總金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).map(Order::getTotal).reduce(Double::sum).get());

11.2 Collector數(shù)據(jù)收集

將流轉(zhuǎn)換為其他形式,coollect 方法作為終端操作, 接收一個(gè)Collector接口的實(shí)現(xiàn),用于給Stream中元素做匯總的方法。最常用的方法,把流中所有元素收集到一個(gè) List, Set 或 Collection中

11.3 集合收集

常用集合收集方法 toList、toSet、toCollection、toMap等

篩選所有有效訂單 并收集訂單列表

// 篩選所有有效訂單并收集訂單列表ordersList.stream().filter((order)->order.getIsValid()==1).collect(Collectors.toList()).forEach(System.out::println);

篩選所有有效訂單并收集訂單號(hào)與訂單金額

// 篩選所有有效訂單 并收集訂單號(hào) 與 訂單金額Map<String,Double>map=ordersList.stream().filter((order)->order.getIsValid()==1).collect(Collectors.toMap(Order::getOrderNo,Order::getTotal));// java8 下對(duì)map進(jìn)行遍歷操作 如果 Map的Key重復(fù),會(huì)報(bào)錯(cuò)map.forEach((k,v)->{System.out.println("k:"+k+":v:"+v);});

十二、匯總

匯總操作在Stream流操作比較常見(jiàn),比如計(jì)算總數(shù),求平均等操作,常用方法如下:

相關(guān)操作如下

篩選所有有效訂單 返回訂單總數(shù)

System.out.println("count結(jié)果:"+ordersList.stream().filter((order)->order.getIsValid()==1).collect(Collectors.counting()));System.out.println("count結(jié)果:"+ordersList.stream().filter((order)->order.getIsValid()==1).count());

返回訂單總金額

System.out.println("訂單總金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).collect(Collectors.summarizingDouble(Order::getTotal)));System.out.println("訂單總金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).mapToDouble(Order::getTotal).sum());System.out.println("訂單總金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).map(Order::getTotal).reduce(Double::sum).get());

返回用戶id=20 有效訂單平均每筆消費(fèi)金額

System.out.println("用戶id=20 有效訂單平均每筆消費(fèi)金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).filter((order->order.getUserId()==20)).collect(Collectors.averagingDouble(Order::getTotal)));System.out.println("用戶id=20 有效訂單平均每筆消費(fèi)金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).filter((order->order.getUserId()==20)).mapToDouble(Order::getTotal).average().getAsDouble());?System.out.println("用戶id=20 有效訂單平均每筆消費(fèi)金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).filter((order->order.getUserId()==20)).collect(Collectors.summarizingDouble(Order::getTotal)).getAverage());

篩選所有有效訂單 并計(jì)算訂單總金額

System.out.println("訂單總金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).collect(Collectors.summingDouble(Order::getTotal)));

十三、最值

篩選所有有效訂單 并計(jì)算最小訂單金額

System.out.println("最小訂單金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).map(Order::getTotal).collect(Collectors.minBy(Double::compare)));

篩選所有有效訂單 并計(jì)算最大訂單金額

// 篩選所有有效訂單 并計(jì)算最大訂單金額System.out.println("最大訂單金額:"+ordersList.stream().filter((order)->order.getIsValid()==1).map(Order::getTotal).collect(Collectors.maxBy(Double::compare)));

十四、分組&分區(qū)

分組

groupingBy 用于將數(shù)據(jù)分組,最終返回一個(gè) Map 類(lèi)型 ,groupingBy 第二參數(shù)用于實(shí)現(xiàn)多級(jí)分組

根據(jù)有效訂單支付狀態(tài)進(jìn)行分組操作

Map<Integer,List<Order>>g01=ordersList.stream().filter((order)->order.getIsValid()==1).collect(Collectors.groupingBy(Order::getStatus));g01.forEach((status,order)->{System.out.println("----------------");System.out.println("訂單狀態(tài):"+status);order.forEach(System.out::println);});

篩選有效訂單,根據(jù)用戶id 和 支付狀態(tài)進(jìn)行分組

Map<Integer,Map<String,List<Order>>>g02=ordersList.stream().filter((order)->order.getIsValid()==1).collect(Collectors.groupingBy(Order::getUserId,Collectors.groupingBy((o)->{if(o.getStatus()==0){return"未支付";}elseif(o.getStatus()==1){return"已支付";}elseif(o.getStatus()==2){return"待發(fā)貨";}elseif(o.getStatus()==3){return"已發(fā)貨";}elseif(o.getStatus()==4){return"已接收";}else{return"已完成";}})));g02.forEach((userId,m)->{System.out.println("用戶id:"+userId+"-->有效訂單如下:");m.forEach((status,os)->{System.out.println("狀態(tài):"+status+"---訂單列表如下:");os.forEach(System.out::println);});System.out.println("-----------------------");});

分區(qū)

分區(qū)與分組的區(qū)別在于,分區(qū)是按照 true 和 false 來(lái)分的,因此partitioningBy 接受的參數(shù)的 lambda 也是?T -> boolean

分區(qū)操作-篩選訂單金額>1000 的有效訂單

Map<Boolean,List<Order>>g03=ordersList.stream().filter((order)->order.getIsValid()==1).collect(Collectors.partitioningBy((o)->o.getTotal()>1000));g03.forEach((b,os)->{System.out.println("分區(qū)結(jié)果:"+b+"--列表結(jié)果:");os.forEach(System.out::println);});

拼接操作-篩選有效訂單并進(jìn)行拼接

StringorderStr=ordersList.stream().filter((order)->order.getIsValid()==1).map(Order::getOrderNo).collect(Collectors.joining(","));System.out.println(orderStr);

十五、流的應(yīng)用

Java8引入Stream流操作,使得對(duì)元素的處理更加的方便快捷,通過(guò)Stream提供的相關(guān)方法很好的結(jié)合Lambda、函數(shù)式接口、方法引用等相關(guān)內(nèi)容,使得流的處理相比較原始集合處理代碼極大簡(jiǎn)化,Stream支持函數(shù)的鏈?zhǔn)秸{(diào)用,代碼上更加緊湊同時(shí)Stream支持的元素的并行化處理提高了程序的執(zhí)行性能。對(duì)于Stream流的應(yīng)用通常在集合元素?cái)?shù)據(jù)處理上特別是對(duì)元素需要進(jìn)行多次處理的情況,同時(shí)對(duì)于函數(shù)式編程的味道更加濃重,也是以后開(kāi)發(fā)的一個(gè)趨勢(shì)。

好了,Java8核心特性之Stream流就介紹到這里了,應(yīng)該是非常詳盡了,希望大家喜歡,多多關(guān)注小樂(lè),小樂(lè)將會(huì)給大家?guī)?lái)更多的技干貨。樂(lè)字節(jié)祝大家端午快樂(lè),學(xué)習(xí)快樂(lè)!

?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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