Class Package{
Long packageId;
String packageName;
List<Service> serviceList;
}
Class Service{
Long serviceId;
String location;
}
List <Package> packageList
上面是我的類,要求是packageId從外部串列packageList和serviceId內部串列(Services每個Package)中收集 wherepackageList.packageName == "Full"和serviceList.location == "Japan". 另外,我需要知道這種記錄是否存在。到目前為止,這就是我將資料收集到HashMap.
HashMap<String,Object> stringObjectHashMap = new HashMap<>();
boolean isFound = packageList.stream()
.filter(package -> "Full".equalsIgnoreCase(package.getPackageName()))
.peek(package ->stringObjectHashMap.put("packageId",package.getPackageId()))
.flatMap(package -> package.getServiceList().stream())
.filter(service -> service.getLocation().equalsIgnoreCase("Japan"))
.peek(service -> stringObjectHashMap.put("serviceId",service.getServiceId()))
.anyMatch(service -> service.getLocation().equalsIgnoreCase("Japan"));
問題是聲納抱怨peek()在流內部使用。它說“根據其 JavaDocs,中間 Stream 操作 java.util.Stream.peek() 的存在主要是為了支持除錯”的目的。
任何人都可以提出更好的解決方案嗎?
樣本輸入:
[
{
"packageId": 13,
"packageName": "Normal",
"serviceList": [
{
"serviceId": "100",
"location": "China"
}
]
},
{
"packageId": 10,
"packageName": "Full",
"serviceList": [
{
"serviceId": "100",
"location": "Spain"
}
]
},
{
"packageId": 5,
"packageName": "Full",
"serviceList": [
{
"serviceId": "100",
"location": "Japan"
}
]
}
]
預期輸出,
"packageId": 5 //this is from outer list
"serviceId": 100 //this is from inner list
注意: "Full" 包可以出現多次。以及位置"Japan"也可以在服務中出現多次。但是packageName與值"Full"和位置"Japan"只能出現一次。
uj5u.com熱心網友回復:
通過使用.flatMap(package -> package.getServiceList().stream()),您實際上將所有包的所有服務整合到一個巨大的流中,并將所有內容混合在一起。
如果我對任務的理解正確,您想選擇名稱為“完整”的所有包,然后,對于每個此類包,選擇該包串列中位置為“日本”的任何服務,如果不存在此類服務,則選擇 null。你可以collect像這樣的地圖:
Map<String,Long> stringObjectMap = packageList.stream().filter(p -> "Full".equalsIgnoreCase(p.getPackageName()))
.collect(Collectors.toMap(
Package::getPackageId,
p -> p.getServiceList().stream()
.filter(service -> "Japan".equalsIgnoreCase(service.getLocation()))
.map(Service::getServiceId)
.findFirst().orElse(null) //you might want to use a stand-in default value here instead of null
));
注意:這將導致 Map 的一些模糊實作,而不是真正的 HashMap,因此您可能需要進行調整。
就個人而言,我發現使用常規 for 回圈更容易解決此類情況。
uj5u.com熱心網友回復:
基于流的解決方案
假設packageNamevalue of"Full"和 a service location等于"Japan"且 every是唯一的,我們可以通過將option of service(位于日本的服務)映射到 apackageId來創建中間映射,并基于它生成最終結果。packageId
Map<String, Long> ids = packageList.stream()
.filter(pack -> "Full".equalsIgnoreCase(pack.getPackageName()))
.collect(Collectors.toMap( // Map<Long, Optional<Service>> - intermediate map
Package::getPackageId,
pack -> pack.getServiceList().stream()
.filter(service -> service.getLocation().equalsIgnoreCase("Japan"))
.findFirst()))
.entrySet().stream()
.filter(entry -> entry.getValue().isPresent())
.findFirst()
.map(entry -> Map.of("packageId", entry.getKey(),
"serviceId", entry.getValue().get().getServiceId()))
.orElse(Collections.emptyMap());
我們能做得更好嗎?目前,我們可以處理所有不需要的資料,以防找到目標包和服務。
我們可以通過使用操作使解決方案短路findFirst(),這也將避免雙重檢查(迭代服務串列兩次)。
實際上,使用簡單的DTO會更方便,例如具有兩個屬性的 Java 16記錄,而不是映射。但是由于您現有的代碼依賴于地圖,因此我將繼續使用地圖,并在內部作為字串資料的中間平均值,我將使用Map.Entry.
Map<String, Long> ids = packageList.stream()
.filter(pack -> "Full".equalsIgnoreCase(pack.getPackageName()))
.map(pack -> Map.entry(pack.getPackageId(), pack.getServiceList().stream()
.filter(service -> service.getLocation().equalsIgnoreCase("Japan"))
.findFirst()))
.filter(entry -> entry.getValue().isPresent())
.findFirst() // <- will produce an intermediate result as Optional<Map.Entry<Long, <Optional<Service>>>
.map(entry -> Map.of("packageId", entry.getKey(),
"serviceId", entry.getValue().get().getServiceId()))
.orElse(Collections.emptyMap());
這個版本的性能更高。"Full" 當遇到包與位于其中的服務的組合時,它將終止流執行"Japan",而不是處理所有資料集。
現在好多了,但有一個問題我們無法解決——不可能消除嵌套流(因為我們需要遍歷服務串列),這會破壞可讀性。
提醒: Java 中引入流是為了以一種簡單易讀的方式構建代碼,而不是相反。舊的普通回圈比順序流執行得更好。
勢在必行的解決方案
現在讓我們使用命令式方法來實作相同的邏輯。
Map<String, Long> result = new HashMap<>();
for (Package pack: packageList) {
if (!pack.getPackageName().equalsIgnoreCase("Full")) continue;
for (Service service: pack.getServiceList()) {
if (service.getLocation().equalsIgnoreCase("Japan")) {
result.put("packageId", pack.getPackageId());
result.put("serviceId", service.getServiceId());
break;
}
}
}
使用普通回圈更容易實作條件邏輯。非常簡潔,易于閱讀,易于維護。
因此,命令式解決方案不僅更簡潔,而且性能更高,因為不需要執行不必要的操作:map 只會更新一次。
您還可以將其與您使用副作用peek()不斷更新地圖的初始方法進行比較。
uj5u.com熱心網友回復:
如果您使用的是 Java 12 或更高版本,則可以通過使用該teeing操作創建. 它們都將回傳一個包含具有預期 id 的固定值。此外,如果您需要知道是否已找到該記錄,您可以檢查您回傳的條目數。PackagesServicesMap<String, Long>StringsMap
Map<String, Long> mapRes = packageList.stream()
.filter(pack -> pack.getPackageName().equalsIgnoreCase("Full") && pack.getServiceList().stream().anyMatch(s -> s.getLocation().equalsIgnoreCase("Japan")))
.collect(Collectors.teeing(
Collectors.toMap(p -> "packageId", Package::getPackageId, (id1, id2) -> id1),
Collectors.flatMapping(pack -> pack.getServiceList().stream(), Collectors.toMap(s -> "serviceId", Service::getServiceId, (id1, id2) -> id1)),
(map1, map2) -> {
map1.putAll(map2);
return map1;
}));
boolean isFound = mapRes.entrySet().size() == 2;
或者,如果 Sonar 向您發出濫用peek操作的信號,并且您仍想使用有狀態的 lambda,盡管不建議這樣做,那么您可以將每個替換peek為map執行精確操作,然后回傳給定元素。這是您的原始代碼,peek替換為map.
boolean isFound = packageList.stream()
.filter(pack -> "Full".equalsIgnoreCase(pack.getPackageName()))
.map(pack -> {
stringObjectHashMap.put("packageId", pack.getPackageId());
return pack;
})
.flatMap(pack -> pack.getServiceList().stream())
.filter(service -> service.getLocation().equalsIgnoreCase("Japan"))
.map(service -> {
stringObjectHashMap.put("serviceId", service.getServiceId());
return service;
})
.anyMatch(service -> service.getLocation().equalsIgnoreCase("Japan"));
這里還有一個測驗代碼的鏈接:
https://ideone.com/uV5PIQ
uj5u.com熱心網友回復:
您可以做的是過濾每個包以查找所有完整包,然后對這些包執行縮減以查找來自日本的服務。
List<Package> fullPackages = packageList
.stream()
.filter(p -> p.getPackageName().equals("Full"))
.toList();
List<Service> fromJapan = fullPackages
.stream()
.flatMap(p -> p.getServiceList().stream())
.filter(service -> service.getLocation().equalsIgnoreCase("Japan"))
.toList();
此外,從它的外觀來看,密鑰不是唯一的,因此它將繼續被覆寫。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/480746.html
上一篇:如何從特定索引開始向后搜索字串的子字串(backwordindexOf)
下一篇:如何將現有代碼實作為函式?
