java - 使用 Stream 将列表转换为另一个列表
问题描述
鉴于,
class Foo {
private Long id;
private String name;
private String category;
private List<String> categories;
// getters & setters
}
我有一个对象列表。
final Foo f1 = new Foo(1L, "a", "c1");
final Foo f2 = new Foo(1L, "a", "c2");
final Foo f3 = new Foo(2L, "a", "c1");
final List<Foo> li = List.of(f1, f2, f3);
看起来像
{[Foo [id=1, name=a, category=c1, categories=null], Foo [id=1, name=a, category=c2, categories=null]], [Foo [id=2, name=a, category=c1, categories=null]]}
我想将其转换为
[Foo [id=1, name=a, category=null, categories=[c1, c2]], Foo [id=2, name=a, category=null, categories=[c1]]]
即将个人整理category
成一个列表 categories
。
这是实现我想要的当前代码。
public static void main(final String[] args) {
final Foo f1 = new Foo(1L, "a", "c1");
final Foo f2 = new Foo(1L, "a", "c2");
final Foo f3 = new Foo(2L, "a", "c1");
final List<Foo> li = List.of(f1, f2, f3);
li.forEach(e -> System.out.println(e));
final Map<Long, List<Foo>> collect = li.stream().collect(Collectors.groupingBy(Foo::getId));
System.out.println(collect);
final List<Foo> grouped = new ArrayList<>();
collect.forEach((k, v) -> {
System.out
.println("key=" + k + "val=" + v.stream().map(e1 -> e1.getCategory()).collect(Collectors.toList()));
final Foo foo = collect.get(k).get(0);
foo.setCategories(v.stream().map(e1 -> e1.getCategory()).collect(Collectors.toList()));
foo.setCategory(null);
grouped.add(foo);
});
System.out.println(grouped);
}
有没有办法单独使用流和 lambdas 来做到这一点而不必分成多个步骤?目标是使这段代码更优雅、更易读,并向读者传达意图。
这个问题本质上类似于 SQL 中的 Group by 和 sum objects,比如 Java lambdas?但并没有帮助我,因为已经完成了聚合,而这里不是聚合。
解决方案
这可以通过实现一个merge
函数来完成,该函数将累积类别列表中的类别,然后使用reduce
流的操作:
class Foo {
static Foo merge(Foo accum, Foo other) {
if (null == accum.categories) {
accum.categories = new ArrayList<>();
if (null != accum.category) {
accum.categories.add(accum.category);
accum.category = null;
}
}
accum.categories.add(other.category);
return accum;
}
}
执行:
static List<Foo> joinedCategoriesReduce(List<Foo> input) {
return input
.stream()
.collect(Collectors.groupingBy(Foo::getId)) // Map<Integer, List<Foo>>
.values().stream() // Stream<List<Foo>>
.map(v -> v.stream() // Stream<Foo>
.reduce(new Foo(v.get(0).getId(), v.get(0).getName(), (String)null), Foo::merge)
)
.collect(Collectors.toList());
}
测试
final Foo f1 = new Foo(1L, "a", "c1");
final Foo f2 = new Foo(1L, "a", "c2");
final Foo f3 = new Foo(2L, "a", "c1");
final List<Foo> li = List.of(f1, f2, f3);
joinedCategoriesReduce(li).forEach(System.out::println);
输出
Foo(id=1, name=a, category=null, categories=[c1, c2])
Foo(id=2, name=a, category=null, categories=[c1])
另一种选择是提供一个接受类别列表的 Foo 构造函数:
// class Foo
public Foo(Long id, String name, List<String> cats) {
this.id = id;
this.name = name;
this.categories = cats;
}
然后可以使用类别列表轻松地将映射条目重新映射到Foo
实例:
static List<Foo> joinedCategoriesMap(List<Foo> input) {
return input
.stream()
.collect(Collectors.groupingBy(Foo::getId))
.values().stream()
.map(v -> new Foo(
v.get(0).getId(),
v.get(0).getName(),
v.stream().map(Foo::getCategory).collect(Collectors.toList()))
)
.collect(Collectors.toList());
}
推荐阅读
- sql-server - 如何减慢所有 SQL 查询?
- excel - 解压缩文件时出现空白excel电子表格
- asp.net - 找不到“Newtonsoft.Json”的版本
- javascript - 没有 jsx 的反应,我可以使用模板字符串而不是 createElement 吗?
- python - 获取与已删除项目相关的 app_id 的任何方式(API)
- pentaho - Pentaho PDI (Kettle/spoon) 文本文件元数据注入
- phpunit - 使用 webDriver 进行覆盖的 Codeception C3 配置
- reactjs - 太多的 React 上下文提供程序
- android - handler.postDelayed() 没有停止
- d3.js - 在未显示的 SVG 元素上使用 ng-bootstrap 的工具提示