我有一个简单的类Foo:
class Foo {
String type;
Integer quantity;
String code;
//...
}
以及该类型的对象列表:
{ type=11, quantity=1, code=A },
{ type=11, quantity=2, code=A },
{ type=11, quantity=1, code=B },
{ type=11, quantity=3, code=C },
{ type=12, quantity=2, code=A },
{ type=12, quantity=1, code=B },
{ type=11, quantity=1, code=B },
{ type=13, quantity=1, code=C },
{ type=13, quantity=3, code=C }
我需要先按类别分组,我可以这样做:
Map<String, List<Foo>> typeGroups = mylist.stream().collect(Collectors.groupingBy(Foo::getType));
下一步是将Map
转换为List<Foo>
,如下所示:
{ type=11, quantity=3, code=A },
{ type=11, quantity=2, code=B },
{ type=11, quantity=3, code=C },
{ type=12, quantity=2, code=A },
{ type=12, quantity=1, code=B },
{ type=13, quantity=4, code=C }
在SQL中,我有这样的查询:
SELECT type, code, SUM(quantity) FROM Foo GROUP BY type, code
让我用简单的英语解释。
我需要最终得到一个Foos列表。每个代码在其组中必须是唯一的,数量必须是其组中具有相同代码的Foos的总和。
我尝试使用groupingBy()
与summingInt()
进行此操作,但无法获得所需的结果。
答案 0 :(得分:3)
您需要使用groupingBy
收藏家,该收藏家根据type
和code
制作的列表进行分组。这是有效的,因为当所有元素的所有元素相同且顺序相同时,两个列表相等。 (请注意,此处的列表仅用作值的容器,其中相等性定义为包含的所有值的相等性且顺序相同;自定义Tuple
类也适用于此处。)
以下代码首先创建一个中间映射Map<List<String>, Integer>
,其中键对应于类型和代码列表,值对应于这些类型和代码的所有数量的总和。然后对该地图进行后处理:每个条目都映射到Foo
,其中type
是密钥的第一个值,quantity
是值,code
是键的第二个值。
Map<List<String>, Integer> map =
myList.stream()
.collect(Collectors.groupingBy(
f -> Arrays.asList(f.getType(), f.getCode()),
Collectors.summingInt(Foo::getQuantity)
));
List<Foo> result =
map.entrySet()
.stream()
.map(e -> new Foo(e.getKey().get(0), e.getValue(), e.getKey().get(1)))
.collect(Collectors.toList());
请注意,上述解决方案使用了2个不同的Stream管道。这可以使用单个Stream管道完成,但它需要一个能够收集到2个不同收集器的收集器。在这种情况下,第一个收集器将保留第一个分组值,第二个收集器将总和数量。不幸的是,Stream API本身没有这样的收集器。这可以使用包含此类收集器的StreamEx库来完成。
在以下代码中,输入列表仍然按照Foo
的类型和代码形成的列表进行分组。收集器有什么变化:我们使用pairing(c1, c2, finisher)
将两个收集器配对,并对收集器的结果应用给定的修整器操作。
first()
,它返回第一个分组Foo
(我们不需要其他收集器,因为我们知道它们将具有相同的类型和代码)。它通过调用collectingAndThen
来包装,以从Optional
收集器中提取first()
值(first()
返回Optional
来处理流的情况是空的,会收集到一个空的Optional
;这是不可能的,所以我们可以安全地调用get()
。summingInt
相加。Foo
和总和的结果合并为一个新的Foo
新数量。最后,我们只需要保留结果地图的值。由于Map.values()
会返回Collection
,因此会将其包装到ArrayList
中以获得最终结果。
List<Foo> result = new ArrayList<>(
myList.stream()
.collect(Collectors.groupingBy(
f -> Arrays.<String>asList(f.getType(), f.getCode()),
MoreCollectors.pairing(
Collectors.collectingAndThen(MoreCollectors.first(), Optional::get),
Collectors.summingInt(Foo::getQuantity),
(f, s) -> new Foo(f.getType(), s, f.getCode())
)
)).values());
答案 1 :(得分:1)
我认为这可以一次完成,假设
public class Foo {
private String type;
private int quantity;
private String code;
public Foo(String type, int quantity, String code) {
this.type = type;
this.code = code;
this.quantity = quantity;
}
public String getType() {return type;}
public int getQuantity() {return quantity;}
public String getCode() {return code;}
}
和
List<Foo> foos = new ArrayList<>();
foos.add(new Foo("11", 1, "A"));
foos.add(new Foo("11", 2, "A"));
foos.add(new Foo("11", 1, "B"));
foos.add(new Foo("11", 3, "C"));
foos.add(new Foo("12", 2, "A"));
foos.add(new Foo("12", 1, "B"));
foos.add(new Foo("11", 1, "B"));
foos.add(new Foo("13", 1, "C"));
foos.add(new Foo("13", 3, "C"));
以下
foos.stream().collect(
toMap(
f -> f.getType() + f.getCode(),
Function.identity(),
(s, a) -> new Foo(
s.getType(),
s.getQuantity() + a.getQuantity(),
s.getCode()))
)
.values();
产生
Foo[type='12'|quantity=2|code='A']
Foo[type='13'|quantity=4|code='C']
Foo[type='12'|quantity=1|code='B']
Foo[type='11'|quantity=3|code='A']
Foo[type='11'|quantity=2|code='B']
Foo[type='11'|quantity=3|code='C']
答案 2 :(得分:0)
使用rxjava帮助:groupBy + flatMap + reduce
Observable.from(()->fooList.stream().iterator())
.groupBy(f->Arrays.asList(f.type,f.code))
.flatMap((Observable<Foo> o)->o
.reduce((a,b)->new Foo(a.type,a.code,a.quantity+b.quantity))
)
.forEach((Foo o) ->
System.out.println("type:" + o.type + " code:" + o.code +" qty:"+ o.quantity)
) ;