英文:
Java 8 how to merge multiple lists by enum type
问题
如何在Java 8中按枚举类型合并多个列表?
我有两个列表:
List<StatusSummary> listA // 因为有4个枚举类型,所以有4个项
List<StatusSummary> listB // 因为有4个枚举类型,所以有4个项
class StatusSummary {
private StatusEnum resourcesStatus; // 4个状态:TypeA、TypeB、TypeC、TypeD
private Integer count;
}
我想要有一个列表,其中包含每个 `resourcesStatus: TypeA, TypeB, TypeC, TypeD` 的4个项,并且 `count` 字段中的元素求和,如下:
List<StatusSummary> listA+B,有4个对象:
(resourcesStatus: TypeA, count: 3), (resourcesStatus: TypeB, count: 99), (resourcesStatus: typeC, count: 322), (resourcesStatus: TypeD, count: 2)
到目前为止,我实现了类似以下的代码:
listA
.getResourcesStatus()
.forEach(la -> listB.getResourcesStatus().stream()
.filter(lb -> lb.getResourcesStatus() == la.getResourcesStatus())
.forEach(lb -> result.add(new StatusSummary()
.count(la.getCount() + lb.getCount())
.status(la.getStatus()))));
英文:
How to merge multiple lists in Java 8 by enum type?
I have 2 lists:
List<StatusSummary> listA //4 items because we have 4 enum types
List<StatusSummary> listB //4 items because we have 4 enum types
class StatusSummary {
private StatusEnum resourcesStatus; //4 statuses: TypeA, TypeB, TypeC, TypeD
private Integer count;
}
I want to have one list consisting of 4 items for each resourcesStatus: TypeA, TypeB, TypeC, TypeD
with the sum of elements in count
field like:
List<StatusSummary> listA+B with 4 objects:
(resourcesStatus: TypeA, count: 3), (resourcesStatus: TypeB, count:99), (resourcesStatus: typeC, count:322), (resourcesStatus: TypeD, count:2)
So far I implemented something like this:
listA
.getResourcesStatus()
.forEach(la -> listB.getResourcesStatus().stream()
.filter(lb -> lb.getResourcesStatus() == la.getResourcesStatus())
.forEach(lb -> result.add(new StatusSummary()
.count(la.getCount() + lb.getCount())
.status(la.getStatus()))));
答案1
得分: 3
你可以使用 Collectors.toMap
。以 ResourcesStatus
为键,使用合并函数将两个具有 count
字段的 StatusSummary
合并为相同的 ResourcesStatus
,然后您将获得类型为 Map<StatusEnum, StatusSummary>
的映射,然后可以使用 .values()
在 ArrayList
中获取映射的值。
List<StatusSummary> newList =
new ArrayList<>(
Stream.of(listA, listB)
.flatMap(Collection::stream)
.collect(Collectors.toMap(
e -> e.getResourcesStatus(),
i -> i,
(a, b) -> new StatusSummary(
a.getResourcesStatus(),
a.getCount() + b.getCount()
)))
.values());
英文:
You can use Collectors.toMap
. Map by ResourcesStatus
and merge two StatusSummary
with count
field in merge-function for the same ResourcesStatus
then you get Map<StatusEnum, StatusSummary>
and then get values of the map using .values()
in ArrayList
.
List<StatusSummary> newList =
new ArrayList<>(
Stream.of(listA, listB)
.flatMap(Collection::stream)
.collect(Collectors.toMap(e -> e.getResourcesStatus(), i -> i,
(a,b) -> new StatusSummary(a.getResourcesStatus(),
a.getCount() + b.getCount())))
.values());
答案2
得分: 2
问题使用 forEach
就我所见,您当前使用 forEach
的解决方案,将流式 API 视为简单的带有附加功能的 for 循环。您违反了 [tag:java-stream] 的 Side-effects 原则,简而言之,该原则指出流不应在执行管道操作时修改另一个集合。我尚未测试您的代码,但这不是您应该处理流的方式。
可能的解决方案之一
您可以使用 Collectors.groupingBy
与 Collectors.summingInt
来获得带有状态和计数总和的 Map<StatusEnum, Integer>
。然后,可以将每个条目转换为 StatusSummary
并将其收集为 List
:
List<StatusSummary> list = Stream.of(listA, listB)
.flatMap(List::stream)
.collect(Collectors.groupingBy( // Map<StatusSummary, Integer>
StatusSummary::getResourcesStatus, // .. 将 StatusEnum 作为键
Collectors.summingInt(StatusSummary::getCount))) // .. 将 Integer 作为值(总和)
.entrySet()
.stream() // Stream<Entry<..>>
.map(e -> new StatusSummary(e.getKey(), e.getValue())) // Stream<StatusSummary>
.collect(Collectors.toList()); // List<StatusSummary>
... 或者使用 Collectors.collectingAndThen
(不少 verbose):
List<StatusSummary> list1 = Stream.of(listA, listB, listC)
.flatMap(List::stream)
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
StatusSummary::getResourcesStatus,
Collectors.summingInt(StatusSummary::getCount)),
map -> map.entrySet().stream()
.map(e -> new StatusSummary(e.getKey(), e.getValue()))
.collect(Collectors.toList())));
英文:
Problem using forEach
As far as I see your current solution using forEach
, you approach to Stream API as it would be a simple for-loop with additional features. You violate the Side-effects principle of [tag:java-stream] which in a nutshell says that a stream shouldn't modify another collection while performing the actions through the pipelines. I haven't tested your code, however, this is not a way you should treat streams.
One of the possible solutions
You can use Collectors.groupingBy
with Collectors.summingInt
to get Map<StatusEnum, Integer>
with the status and a sum of counts. Each entry then can be transformed into StatusSummary
and colleted as List
:
List<StatusSummary> list = Stream.of(listA, listB)
.flatMap(List::stream)
.collect(Collectors.groupingBy( // Map<StatusSummary, Integer>
StatusSummary::getResourcesStatus, // .. StatusEnum as key
Collectors.summingInt(StatusSummary::getCount))) // .. Integer as value (sum)
.entrySet()
.stream() // Stream<Entry<..>>
.map(e -> new StatusSummary(e.getKey(), e.getValue())) // Stream<StatusSummary>
.collect(Collectors.toList()); // List<StatusSummary>
... or using Collectors.collectingAndThen
(not less verbose):
List<StatusSummary> list1 = Stream.of(listA, listB, listC)
.flatMap(List::stream)
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
StatusSummary::getResourcesStatus,
Collectors.summingInt(StatusSummary::getCount)),
map -> map.entrySet().stream()
.map(e -> new StatusSummary(e.getKey(), e.getValue()))
.collect(Collectors.toList())));
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论