使用 Java 流对嵌套对象进行分组和排序
Group and Sort nested Objects using Java Streams
我有一个包含 DTO
个对象的列表,其中包含嵌套列表字段。
目的是通过id
字段分组,然后合并,然后排序 使用 Streams 的列表 API.
class DTO {
private Long id;
private List<ItemDTO> items;
}
class ItemDTO {
private Long priority;
private Long value;
}
// input
List<DTO> dtoList = List.of(
DTO(1, List.of(ItemDTO(1, 1), ItemDTO(7, 2))),
DTO(2, List.of(ItemDTO(1, 1), ItemDTO(2, 2))),
DTO(1, List.of(ItemDTO(10, 3), ItemDTO(1, 4)))
);
我需要分组这些具有相同id
字段的嵌套对象并且合并[=28=中的所有项目]按字段priority
.
降序
这个 dtoList
的最终结果将是这样的:
// output
List<DTO> resultList = [
DTO(1, List.of(ItemDTO(10,3), ItemDTO(7,2), ItemDTO(1,1), ItemDTO(1,4)),
DTO(2, List.of(ItemDTO(2,2), ItemDTO(1,1),
];
我们能否通过 Streams 实现此目标 API?
您可以通过按 id
对数据进行分组来创建中间映射,然后将每个条目转换为新的 DTO
对象。
为此,您可以组合使用 built-in 个收集器 groupingBy()
and flatMapping()
来创建中间地图。
为了对每个 id
映射的项目进行排序,flatMapping()
与 collectionAndThen()
结合使用。
public static void main(String[] args) {
// input
List<DTO> dtoList = List.of(
new DTO(1L, List.of(new ItemDTO(1L, 1L), new ItemDTO(7L, 2L))),
new DTO(2L, List.of(new ItemDTO(1L, 1L), new ItemDTO(2L, 2L))),
new DTO(1L, List.of(new ItemDTO(10L, 3L), new ItemDTO(1L, 4L)))
);
List<DTO> result = dtoList.stream()
.collect(Collectors.groupingBy(DTO::getId,
Collectors.collectingAndThen(
Collectors.flatMapping(dto -> dto.getItems().stream(), Collectors.toList()),
(List<ItemDTO> items) -> {
items.sort(Comparator.comparing(ItemDTO::getPriority).reversed());
return items;
})))
.entrySet().stream()
.map(entry -> new DTO(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
result.forEach(System.out::println);
}
输出
DTO{id = 1, items = [ItemDTO{10, 3}, ItemDTO{7, 2}, ItemDTO{1, 1}, ItemDTO{1, 4}]}
DTO{id = 2, items = [ItemDTO{2, 2}, ItemDTO{1, 1}]}
正如 @shmosel 指出的那样,flatMapping()
是 Java 9 的好处之一。您可能还认为这是一个提醒,也许是时候转向 Java 9 提供的模块化系统和其他有用的功能了。
完全符合Java8的版本将如下所示:
List<DTO> result = dtoList.stream()
.collect(Collectors.groupingBy(DTO::getId,
Collectors.collectingAndThen(
Collectors.mapping(DTO::getItems, Collectors.toList()),
(List<List<ItemDTO>> items) ->
items.stream().flatMap(List::stream)
.sorted(Comparator.comparing(ItemDTO::getPriority).reversed())
.collect(Collectors.toList())
)))
.entrySet().stream()
.map(entry -> new DTO(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
我将从一个简单的分组开始,以获得一个映射 Map<Long,List<DTO>>
并流过该映射的条目并将每个条目映射到一个新的 DTO。您可以提取方法/函数来对 ItemDTO
进行排序:
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
....
Function<List<DTO>, List<ItemDTO>> func =
list -> list.stream()
.map(DTO::getItems)
.flatMap(List::stream)
.sorted(Comparator.comparing(ItemDTO::getPriority,Comparator.reverseOrder()))
.collect(Collectors.toList());
List<DTO> result =
dtoList.stream()
.collect(Collectors.groupingBy(DTO::getId))
.entrySet().stream()
.map(entry -> new DTO(entry.getKey(), func.apply(entry.getValue())))
//.sorted(Comparator.comparingLong(DTO::getId)) if the resulting list need to be sorted by id
.collect(Collectors.toList());
依我看,这是最简单的方法。我假设您已经为您定义了合适的 getters 类.
- 简单地转换为以 id 为键的地图。
- 合并适当的列表
- 和 return 值并转换为 ArrayList。
List<DTO> results = new ArrayList<>(dtoList.stream().collect(
Collectors.toMap(DTO::getId, dto -> dto, (a, b) -> {
a.getItems().addAll(b.getItems());
return a;
})).values());
然后根据您的要求简单地对它们进行排序。与在流构造中执行此操作相比,这不会花费更多时间,但在我看来更简洁。
for (DTO d: results) {
d.getItems().sort(Comparator.comparing(ItemDTO::getPriority)
.reversed());
}
results.forEach(System.out::println);
打印(对两个 类 使用简单的 toString
)
DTO[1, [ItemDTO[10, 3], ItemDTO[7, 2], ItemDTO[1, 1], ItemDTO[1, 4]]]
DTO[2, [ItemDTO[2, 2], ItemDTO[1, 1]]]
注意:List.of
是不可变的,因此您无法更改它们。我会在你的列表结构中使用 new ArrayList<>(List.of(...))
。
我有一个包含 DTO
个对象的列表,其中包含嵌套列表字段。
目的是通过id
字段分组,然后合并,然后排序 使用 Streams 的列表 API.
class DTO {
private Long id;
private List<ItemDTO> items;
}
class ItemDTO {
private Long priority;
private Long value;
}
// input
List<DTO> dtoList = List.of(
DTO(1, List.of(ItemDTO(1, 1), ItemDTO(7, 2))),
DTO(2, List.of(ItemDTO(1, 1), ItemDTO(2, 2))),
DTO(1, List.of(ItemDTO(10, 3), ItemDTO(1, 4)))
);
我需要分组这些具有相同id
字段的嵌套对象并且合并[=28=中的所有项目]按字段priority
.
这个 dtoList
的最终结果将是这样的:
// output
List<DTO> resultList = [
DTO(1, List.of(ItemDTO(10,3), ItemDTO(7,2), ItemDTO(1,1), ItemDTO(1,4)),
DTO(2, List.of(ItemDTO(2,2), ItemDTO(1,1),
];
我们能否通过 Streams 实现此目标 API?
您可以通过按 id
对数据进行分组来创建中间映射,然后将每个条目转换为新的 DTO
对象。
为此,您可以组合使用 built-in 个收集器 groupingBy()
and flatMapping()
来创建中间地图。
为了对每个 id
映射的项目进行排序,flatMapping()
与 collectionAndThen()
结合使用。
public static void main(String[] args) {
// input
List<DTO> dtoList = List.of(
new DTO(1L, List.of(new ItemDTO(1L, 1L), new ItemDTO(7L, 2L))),
new DTO(2L, List.of(new ItemDTO(1L, 1L), new ItemDTO(2L, 2L))),
new DTO(1L, List.of(new ItemDTO(10L, 3L), new ItemDTO(1L, 4L)))
);
List<DTO> result = dtoList.stream()
.collect(Collectors.groupingBy(DTO::getId,
Collectors.collectingAndThen(
Collectors.flatMapping(dto -> dto.getItems().stream(), Collectors.toList()),
(List<ItemDTO> items) -> {
items.sort(Comparator.comparing(ItemDTO::getPriority).reversed());
return items;
})))
.entrySet().stream()
.map(entry -> new DTO(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
result.forEach(System.out::println);
}
输出
DTO{id = 1, items = [ItemDTO{10, 3}, ItemDTO{7, 2}, ItemDTO{1, 1}, ItemDTO{1, 4}]}
DTO{id = 2, items = [ItemDTO{2, 2}, ItemDTO{1, 1}]}
正如 @shmosel 指出的那样,flatMapping()
是 Java 9 的好处之一。您可能还认为这是一个提醒,也许是时候转向 Java 9 提供的模块化系统和其他有用的功能了。
完全符合Java8的版本将如下所示:
List<DTO> result = dtoList.stream()
.collect(Collectors.groupingBy(DTO::getId,
Collectors.collectingAndThen(
Collectors.mapping(DTO::getItems, Collectors.toList()),
(List<List<ItemDTO>> items) ->
items.stream().flatMap(List::stream)
.sorted(Comparator.comparing(ItemDTO::getPriority).reversed())
.collect(Collectors.toList())
)))
.entrySet().stream()
.map(entry -> new DTO(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
我将从一个简单的分组开始,以获得一个映射 Map<Long,List<DTO>>
并流过该映射的条目并将每个条目映射到一个新的 DTO。您可以提取方法/函数来对 ItemDTO
进行排序:
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
....
Function<List<DTO>, List<ItemDTO>> func =
list -> list.stream()
.map(DTO::getItems)
.flatMap(List::stream)
.sorted(Comparator.comparing(ItemDTO::getPriority,Comparator.reverseOrder()))
.collect(Collectors.toList());
List<DTO> result =
dtoList.stream()
.collect(Collectors.groupingBy(DTO::getId))
.entrySet().stream()
.map(entry -> new DTO(entry.getKey(), func.apply(entry.getValue())))
//.sorted(Comparator.comparingLong(DTO::getId)) if the resulting list need to be sorted by id
.collect(Collectors.toList());
依我看,这是最简单的方法。我假设您已经为您定义了合适的 getters 类.
- 简单地转换为以 id 为键的地图。
- 合并适当的列表
- 和 return 值并转换为 ArrayList。
List<DTO> results = new ArrayList<>(dtoList.stream().collect(
Collectors.toMap(DTO::getId, dto -> dto, (a, b) -> {
a.getItems().addAll(b.getItems());
return a;
})).values());
然后根据您的要求简单地对它们进行排序。与在流构造中执行此操作相比,这不会花费更多时间,但在我看来更简洁。
for (DTO d: results) {
d.getItems().sort(Comparator.comparing(ItemDTO::getPriority)
.reversed());
}
results.forEach(System.out::println);
打印(对两个 类 使用简单的 toString
)
DTO[1, [ItemDTO[10, 3], ItemDTO[7, 2], ItemDTO[1, 1], ItemDTO[1, 4]]]
DTO[2, [ItemDTO[2, 2], ItemDTO[1, 1]]]
注意:List.of
是不可变的,因此您无法更改它们。我会在你的列表结构中使用 new ArrayList<>(List.of(...))
。