Fab*_* B. 5 java grouping java-8 java-stream
我有一个简单的类 Foo:
class Foo {
String type;
Integer quantity;
String code;
//...
}
Run Code Online (Sandbox Code Playgroud)
以及该类型的对象列表:
{ 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 }
Run Code Online (Sandbox Code Playgroud)
我需要先按类型分组,我可以这样做:
Map<String, List<Foo>> typeGroups = mylist.stream().collect(Collectors.groupingBy(Foo::getType));
Run Code Online (Sandbox Code Playgroud)
下一步是将其转换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 }
Run Code Online (Sandbox Code Playgroud)
在 SQL 中,我有这样的查询:
SELECT type, code, SUM(quantity) FROM Foo GROUP BY type, code
Run Code Online (Sandbox Code Playgroud)
让我也用简单的英语解释一下。
我需要得到一个 Foos 列表。每个人的代码在其组中必须是唯一的,数量必须是其组中具有相同代码的 Foo 的总和。
我尝试使用groupingBy()withsummingInt()但无法获得所需的结果。
您需要使用groupingBy根据type和 制作的列表进行分组的收集器code。这是有效的,因为当两个列表的所有元素都相等且顺序相同时,两个列表就相等。(请注意,此处的列表仅用作值的容器,其相等性定义为包含的所有值相等且顺序相同;自定义类Tuple也可以在此处使用)。
以下代码首先创建一个中间映射Map<List<String>, Integer>,其中键对应于类型和代码的列表,值对应于这些类型和代码的所有数量的总和。然后对该映射进行后处理:每个条目都映射到 a,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());
Run Code Online (Sandbox Code Playgroud)
请注意,上述解决方案使用了 2 个不同的 Stream 管道。这可以使用单个 Stream 管道来完成,但需要一个能够收集到 2 个不同收集器中的收集器。在这种情况下,第一个收集器将仅保留第一个分组值,第二个收集器将对数量求和。不幸的是,Stream API 本身没有这样的收集器。这可以使用包含此类收集器的StreamEx库来完成。
在下面的代码中,输入列表仍然按 的类型和代码形成的列表进行分组Foo。改变的是收集器:我们使用pairing(c1, c2, finisher)配对两个收集器并对收集器的结果应用给定的完成器操作。
first()返回第一个分组Foo(我们不需要其他收集器,因为我们知道它们将具有相同的类型和代码)。它包含一个调用,以从收集器中collectingAndThen提取值(返回 an来处理流为空的情况并将收集到一个空中;这在这里是不可能的,因此我们可以安全地调用)。Optionalfirst()first()OptionalOptionalget()summingInt。Foo第一个收集器返回的结果与求和的结果组合成一个新的Foo具有新数量的结果。最后,我们只需要保留结果图的值。由于Map.values()返回 a Collection,因此将其包装到 a 中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());
Run Code Online (Sandbox Code Playgroud)