如果你在 Foo
类中实现了特殊的、专用的构造函数,以及一致实现的 hashCode
和 equals
方法,如下所示:
public Foo(Foo that) {
this.category = that.category;
this.amount = 0;
this.price = 0;
}
public int hashCode() {
return Objects.hashCode(category);
}
public boolean equals(Object another) {
if (another == this) return true;
if (!(another instanceof Foo)) return false;
Foo that = (Foo) another;
return Objects.equals(this.category, that.category);
}
以上的
hashCode
和
equals
实现使您能够在地图中使用
Foo
作为有意义的键(否则您的地图将无法正常工作)。
现在,借助于
Foo
中执行
amount
和
price
属性聚合的新方法,您可以在两步内完成所需操作。首先是该方法:
public void aggregate(Foo that) {
this.amount += that.amount;
this.price += that.price;
}
现在是最终解决方案:
Map<Foo, List<Foo>> result = fooList.stream().collect(
Collectors.collectingAndThen(
Collectors.groupingBy(Foo::new), // works: special ctor, hashCode & equals
m -> { m.forEach((k, v) -> v.forEach(k::aggregate))
编辑:缺少了一些观察结果...
一方面,这个解决方案强制你使用一个实现了hashCode
和equals
的方法,并且将两个不同的Foo
实例视为相等,如果它们属于同一个category
。也许这不是您想要的,或者您已经有了一个考虑更多或其他属性的实现方式。
另一方面,使用Foo
作为映射的键,该映射用于按其属性之一对实例进行分组,这是非常不常见的用例。我认为最好只使用category
属性来按类别分组,并拥有两个映射:Map<String,List<Foo>>
用于保留组,以及Map<String,Foo>
用于保留聚合的price
和amount
,在这两种情况下,关键字都是类别。
此外,这个解决方案在把条目放入映射后改变了映射的键。这是危险的,因为这可能会破坏映射。但是,在这里,我只改变了Foo
的属性,这些属性既不参与hashCode
也不参与equals
的实现。我认为,在这种情况下,这种风险是可以接受的,因为该要求不常见。
Collectors.summarizingLong
吗? - dehasiBiCollector
,这将使您的生活更轻松。 - Eugene