[英]How to groupBy object properties and map to another object using Java 8 Streams?
假設我有一組碰碰車,它們的側面有尺寸、顏色和標識符(“汽車代碼”)。
class BumperCar {
int size;
String color;
String carCode;
}
現在我需要將碰碰車映射到一個DistGroup
對象List
, DistGroup
對象包含屬性size
、 color
和汽車代碼List
。
class DistGroup {
int size;
Color color;
List<String> carCodes;
void addCarCodes(List<String> carCodes) {
this.carCodes.addAll(carCodes);
}
}
例如,
[
BumperCar(size=3, color=yellow, carCode=Q4M),
BumperCar(size=3, color=yellow, carCode=T5A),
BumperCar(size=3, color=red, carCode=6NR)
]
應該導致:
[
DistGroup(size=3, color=yellow, carCodes=[ Q4M, T5A ]),
DistGroup(size=3, color=red, carCodes=[ 6NR ])
]
我嘗試了以下操作,這實際上做了我想要它做的事情。 但問題是它實現了中間結果(到Map
),我也認為它可以立即完成(可能使用mapping
或collectingAndThen
或reducing
或其他東西),從而產生更優雅的代碼。
List<BumperCar> bumperCars = …;
Map<SizeColorCombination, List<BumperCar>> map = bumperCars.stream()
.collect(groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())));
List<DistGroup> distGroups = map.entrySet().stream()
.map(t -> {
DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
d.addCarCodes(t.getValue().stream()
.map(BumperCar::getCarCode)
.collect(toList()));
return d;
})
.collect(toList());
如何在不使用中間結果的變量的情況下獲得所需的結果?
編輯:如何在不實現中間結果的情況下獲得所需的結果? 我只是在尋找一種不會實現中間結果的方法,至少不會在表面上實現。 這意味着我不喜歡使用這樣的東西:
something.stream()
.collect(…) // Materializing
.stream()
.collect(…); // Materializing second time
當然,如果可以的話。
請注意,為簡潔起見,我省略了 getter 和構造函數。 您還可以假設equals
和hashCode
方法已正確實現。 另請注意,我使用的是用作分組鍵的SizeColorCombination
。 這個類顯然包含屬性size
和color
。 也可以使用像Tuple
、 Pair
、 Entry
類或任何其他表示兩個任意值組合的類。
編輯:另請注意,當然可以使用 ol' skool for 循環,但這不在此問題的范圍內。
如果我們假設DistGroup
具有基於size
和color
hashCode/equals
,您可以這樣做:
bumperCars
.stream()
.map(x -> {
List<String> list = new ArrayList<>();
list.add(x.getCarCode());
return new SimpleEntry<>(x, list);
})
.map(x -> new DistGroup(x.getKey().getSize(), x.getKey().getColor(), x.getValue()))
.collect(Collectors.toMap(
Function.identity(),
Function.identity(),
(left, right) -> {
left.getCarCodes().addAll(right.getCarCodes());
return left;
}))
.values(); // Collection<DistGroup>
只需將兩步合二為一:
List<DistGroup> distGroups = bumperCars.stream()
.collect(Collectors.groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())))
.entrySet().stream()
.map(t -> {
DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
d.addCarCodes(t.getValue().stream().map(BumperCar::getCarCode).collect(Collectors.toList()));
return d;
})
.collect(Collectors.toList());
如果您可以使用groupingBy
兩次使用屬性並將值映射為代碼List
,那么您的中間變量會好得多,例如:
Map<Integer, Map<String, List<String>>> sizeGroupedData = bumperCars.stream()
.collect(Collectors.groupingBy(BumperCar::getSize,
Collectors.groupingBy(BumperCar::getColor,
Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))));
並簡單地使用forEach
添加到最終列表中:
List<DistGroup> distGroups = new ArrayList<>();
sizeGroupedData.forEach((size, colorGrouped) ->
colorGrouped.forEach((color, carCodes) -> distGroups.add(new DistGroup(size, color, carCodes))));
注意:我已經更新了您的構造函數,使其接受卡片代碼列表。
DistGroup(int size, String color, List<String> carCodes) {
this.size = size;
this.color = color;
addCarCodes(carCodes);
}
進一步將第二個解決方案組合成一個完整的陳述(盡管我自己會很喜歡forEach
老實說):
List<DistGroup> distGroups = bumperCars.stream()
.collect(Collectors.groupingBy(BumperCar::getSize,
Collectors.groupingBy(BumperCar::getColor,
Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))))
.entrySet()
.stream()
.flatMap(a -> a.getValue().entrySet()
.stream().map(b -> new DistGroup(a.getKey(), b.getKey(), b.getValue())))
.collect(Collectors.toList());
可以通過使用收集由BiConsumer
稱取(HashMap<SizeColorCombination, DistGroup> res, BumperCar bc)
作為參數
Collection<DistGroup> values = bumperCars.stream()
.collect(HashMap::new, (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) -> {
SizeColorCombination dg = new SizeColorCombination(bc.color, bc.size);
DistGroup distGroup = res.get(dg);
if(distGroup != null) {
distGroup.addCarCode(bc.carCode);
}else {
List<String> codes = new ArrayList();
distGroup = new DistGroup(bc.size, bc.color, codes);
res.put(dg, distGroup);
}
},HashMap::putAll).values();
查看我的庫AbacusUtil :
StreamEx.of(bumperCars)
.groupBy(c -> Tuple.of(c.getSize(), c.getColor()), BumperCar::getCarCode)
.map(e -> new DistGroup(e.getKey()._1, e.getKey()._2, e.getValue())
.toList();
我想出了另一個解決方案。
首先,我們稍微調整一下DistGroup
類:
class DistGroup {
private int size;
private String color;
private List<String> carCodes;
private DistGroup(int size, String color, List<String> carCodes) {
this.size = size;
this.color = color;
this.carCodes = carCodes;
}
// So we can use a method reference nicely
public static DistGroup ofBumperCar(BumperCar car) {
return new DistGroup(car.size(), car.color(), new ArrayList<>(Arrays.asList(car.carCode())));
}
public DistGroup merge(DistGroup other) {
assert size == other.size;
assert Objects.equals(color, other.color);
this.carCodes.addAll(other.carCodes);
return this;
}
}
請注意,我刪除了addCarCodes
方法,因為它不需要。
現在我們實際上可以使用Collectors::toMap
方法很容易地獲得所需的結果。
Collection<DistGroup> dists = cars.stream()
.collect(Collectors.toMap(
car -> new SizeColorGroup(car.size(), car.color()),
DistGroup::ofBumperCar,
DistGroup::merge
))
.values();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.