原稿于3.2日釋出,然而事情并沒完,我發現必須得補充一個常見的坑,為了防止大家采坑,我在更新了本文的後兩段。
上一篇中我們通過一個執行個體看到了Java8 Stream API 相較于傳統的的Java 集合操作的簡潔與優勢,本篇我們依然借助于一個實際的例子來看看Java8 Stream API 如何抽取及收集資料。
備注:上一篇内容:如何用Java8 Stream API找到心儀的女朋友
目标 & 背景
我們以“處理訂單資料”為例,假設我們的應用是一個分布式應用,有"訂單應用","物流應用","商品應用”等都是獨立的服務。本次我們的目的需要展示訂單清單完整資料:
- 1.查詢訂單清單。
- 2.批量查詢物流資訊。
- 3.将物流資訊填充到訂單主資訊中。
假設我們定義了一個訂單類,具有幾個關鍵的屬性:訂單号,狀态,訂單價,快遞資訊。如下所示:
class Order{
String orderSeq;
String status;
double totalPrice;
String expressInfo;
// 省略get,set及hashCode等方法
}
我們定義了一個快遞資訊類,幾個關鍵的屬性:訂單号,物流公司,物流單号,物流狀态。如下所示:
class ExpressInfo{
String orderSeq;
String expressName;
String expressNo;
String createTime;
String statusInfo;
// 省略get,set及hashCode等方法
}
Java7 實作
擷取訂單清單 & 抽取訂單号
List<Order> orderList = getOrderList();
// 抽取 訂單号
List<String> orderSeqList = new ArrayList<>();
for (Order order : orderList) {
orderSeqList.add(order.getOrderSeq());
}
這裡我們擷取了訂單清單
orderList
,此時
expressInfo
裡邊是沒有資料的。這裡抽取單号依然是Java傳統的寫法。
批量查詢快遞資訊 & 組裝 訂單-快遞資訊 map
由于我們是通過調用遠端服務來擷取快遞資訊,為了減少網絡通信次數,我們采取批量查詢的方式。這也是為什麼,上一步中我們要抽取訂單号
下面我們來擷取快遞資訊
// 調用遠端服務,
List<ExpressInfo> expressInfos = RpcGetExpressInfoBatch(orderSeqList);
// 組裝 訂單-快遞 關系map
Map<String,String> orderExpressMap = new HashMap<>();
for(ExpressInfo e: expressInfos){
orderExpressMap.put(e.getOrderSeq(),e.getStatusInfo());
}
這裡組裝map,也依然是Java7常用的寫法。
組合資料,将快遞資訊填充進訂單
for(Order order:orderList){
String expressInfo = orderExpressMap.get(order.getOrderSeq());
order.setExpressInfo(expressInfo);
}
至此,我們使用Java7 的寫法,完成了開篇設定的目标。下面我們看Java8的寫法
Java8 實作
// 擷取清單
List<Order> orderList = getOrderList();
// 抽取單号
List<String> orderSeqs = orderList.stream()
.map(Order::getOrderSeq)
.collect(Collectors.toList());
這裡我們使用了
stream.map
,在
map()
中,我們的寫法是
Order::getOrderSeq
表示調用
Order
對象的
getOrderSeq()
方法來抽取訂單号。
這裡的
::
叫“方法引用”,是Java8中的新寫法。
在
map()
後面緊跟的是
collect
收集器,他将抽取的資料
toList()
,于是我們得到了最終的List。
下面我們仍然是通過遠端調用來擷取快遞資訊,然後使用Java8的文法建立一個 訂單-快遞 關聯資訊的map。
List<ExpressInfo> expressInfos = RpcGetExpressInfoBatch(orderSeqList);
Map<String,String> orderExpressMap =expressInfos.stream()
.collect(Collectors.toMap(ExpressInfo::getOrderSeq,ExpressInfo::getStatusInfo));
這裡代碼比Java7的要少吧,且一目了然,這裡用
strean().collect
來收集資料,收內建什麼形式呢?看名知意,
Collectors.toMap
收內建Map,收集什麼資料呢?
toMap()
中,寫了
ExpressInfo::getOrderSeq
及
ExpressInfo::getStatusInfo
,即:抽取
orderSeq
作為key,
statusInfo
作為value。
通過這裡的
Collectors.toMap
收集器,我們很友善的獲得了 訂單-物流關系資料
map
。
經過上面的兩步,我們得到了符合我們要求的資料,現在我們需要将快遞資訊填充進訂單,代碼如下:
orderList.stream().forEach(o -> o.setExpressInfo(orderExpressMap.get(o.getOrderSeq())));
你沒看錯,就隻有這麼一行。
補充說明: key 重複的處理
上面的代碼中,我們使用如下代碼來從list中收集Map:
Collectors.toMap(ExpressInfo::getOrderSeq,ExpressInfo::getStatusInfo))
這裡如果expressInfos這個list中有重複資料,那麼orderSeq就會有重複的,這種情況下就會報一個錯:
java.lang.IllegalStateException: Duplicate key
想模拟這個錯誤很簡單,往expressInfos中add兩條一樣的資料,運作上面的抽取map代碼就會報錯。
為了解決這個問題,我們需要這樣寫:
List<ExpressInfo> expressInfos = RpcGetExpressInfoBatch(orderSeqList);
Map<String,String> orderExpressMap =expressInfos.stream()
.collect(Collectors.toMap(ExpressInfo::getOrderSeq,ExpressInfo::getStatusInfo,(v1,v2)->v1));
這裡,我們主要的變動是這一句:
Collectors.toMap(ExpressInfo::getOrderSeq,ExpressInfo::getStatusInfo,(v1,v2)->v1)
我們在在
Collectors.toMap
中增加了一個lumda表達式:
(v1,v2)->v1
意思是當有重複資料v1,v2出現的時候,我們選擇v1。當然,你可以根據你的情況選擇v2。
選擇v1,或者v2,需要根據你的業務來權衡。比如list中的資料按照時間先後排序的,我們取最新的就選v2,反之取v1。
總結
本節,我們使用Java8 Stream API,完成了資料的抽取和收集,使用了
map()
,和
collect()
來完成資料的抽取和收集,并了解了兩種收取方式
toList
和
toMap
。同時我們也知道應對list中有重複資料導緻報錯的問題。是以,以後如果有人問你"Java8 stream 如何擷取對象的某個屬性list啊?",“java8 stream 如何擷取指定資料組裝成map啊?”,你就可以把本文中的方法告訴他了。
除此之外,Java8 Streap API 還有分組 等功能,後面再說。你也可以關注我的公衆号,第一時間收到推送。
Java8系列- 如何用Java8 Stream API找到心儀的女朋友
SpringMVC是怎麼工作的,SpringMVC的工作原理
Mybatis Mapper接口是如何找到實作類的-源碼分析
小程式雲開發:菜鳥也能全棧做産品
CORS詳解,CORS原理分析
工作6年,失業19天
Docker & k8s 系列一:快速上手docker
Docker & k8s 系列二:本機k8s環境搭建
Docker & k8s 系列三:在k8s中部署單個服務執行個體
感謝您的閱讀,歡迎您評論交流。PS:部落格園已停更,後續請通過郵件或者公衆号聯系