问题描述
我有两个对象如下:
public class A {
private Integer id;
private String name;
private List<B> list;
public A(Integer id, String name, List<B> list) {
this.id = id;
this.name = name;
this.list = list;
}
//getters and setters
}
和
public class B {
private Integer id;
private String name;
public B(Integer id, String name) {
this.id = id;
this.name = name;
}
//getters and setters
}
因此,A持有B的列表,并且有一个填充的A列表如下:
So, A holds a list of B and there is a list of A populated as follows:
List<A> list = new ArrayList<>();
list.add(new A(1, "a_one", Arrays.asList(new B(1, "b_one"), new B(2, "b_two"))));
list.add(new A(2, "a_two", Arrays.asList(new B(2, "b_two"))));
list.add(new A(1, "a_one", Arrays.asList(new B(3, "b_three"))));
list.add(new A(2, "a_two", Arrays.asList(new B(4, "b_four"), new B(5, "b_five"))));
list.add(new A(3, "a_three", Arrays.asList(new B(4, "b_four"), new B(5, "b_five"))));
我想通过合并具有相同ID的对象来获取新列表。结果列表必须如下:
I want to acquire a new list by merging A objects with same ids. Result list must be like that:
[
A(1, a_one, [B(1, b_one), B(2, b_two), B(3, b_three)]),
A(2, a_two, [B(2, b_two), B(4, b_four), B(5, b_five)]),
A(3, a_three, [B(4, b_four), B(5, b_five)])
]
我确实设法将列表与以下代码合并:
I did manage to merge the list with the following code:
List<A> resultList = new ArrayList<>();
list.forEach(a -> {
if (resultList.stream().noneMatch(ai -> ai.getId().equals(a.getId()))) {
a.setList(list.stream().filter(ai -> ai.getId().equals(a.getId()))
.flatMap(ai -> ai.getList().stream()).collect(Collectors.toList()));
resultList.add(a);
}
});
我的问题是,有没有正确的方法通过使用流收集器来做到这一点?
My question is, is there any proper way to do this by using stream collectors?
推荐答案
如果您不想使用额外的功能,您可以执行以下操作,它可读且易于理解,首先按ID分组,创建一个新对象,列表中有第一个元素,然后加入所有B类,最后收集A。
If you don't want to use extra functions you can do the following, it's readable and easy to understand, first group by id, create a new object with the first element in the list and then join all the B's classes to finally collect the A's.
List<A> result = list.stream()
.collect(Collectors.groupingBy(A::getId))
.values().stream()
.map(grouped -> new A(grouped.get(0).getId(), grouped.get(0).getName(),
grouped.stream().map(A::getList).flatMap(List::stream)
.collect(Collectors.toList())))
.collect(Collectors.toList());
另一种方法是使用二元运算符和方法。这里使用java 8可选类在 fst 为空时第一次创建新的A.
Another way is to use a binary operator and the Collectors.groupingBy method. Here you use the java 8 optional class to create the new A the first time when fst is null.
BinaryOperator<A> joiner = (fst, snd) -> Optional.ofNullable(fst)
.map(cur -> { cur.getList().addAll(snd.getList()); return cur; })
.orElseGet(() -> new A(snd.getId(), snd.getName(), new ArrayList<>(snd.getList())));
Collection<A> result = list.stream()
.collect(Collectors.groupingBy(A::getId, Collectors.reducing(null, joiner)))
.values();
如果你不喜欢在短lambda中使用return(看起来不太好) only选项只是一个过滤器,因为java没有提供另一种方法,比如stream's peek(注意:某些IDE突出显示'简化'表达式,并且不应该在过滤器中进行突变[但我认为在地图中也没有]。)
If you don't like to use return in short lambdas (doesn't look that well) the only option is a filter because java does not provide another method like stream's peek (note: some IDEs highlight to 'simplify' the expression and mutations shouldn't be made in filter [but i think in maps neither]).
BinaryOperator<A> joiner = (fst, snd) -> Optional.ofNullable(fst)
.filter(cur -> cur.getList().addAll(snd.getList()) || true)
.orElseGet(() -> new A(snd.getId(), snd.getName(), new ArrayList<>(snd.getList())));
您也可以使用此连接作为通用方法,并创建一个从左到右的reducer与一个消费者允许加入用初始化函数创建的新的可变对象。
You can also use this joiner as a generic method and create a left to right reducer with a consumer that allows to join the new mutable object created with the initializer function.
public class Reducer {
public static <A> Collector<A, ?, A> reduce(Function<A, A> initializer,
BiConsumer<A, A> combiner) {
return Collectors.reducing(null, (fst, snd) -> Optional.ofNullable(fst)
.map(cur -> { combiner.accept(cur, snd); return cur; })
.orElseGet(() -> initializer.apply(snd)));
}
public static <A> Collector<A, ?, A> reduce(Supplier<A> supplier,
BiConsumer<A, A> combiner) {
return reduce((ign) -> supplier.get(), combiner);
}
}
并像
Collection<A> result = list.stream()
.collect(Collectors.groupingBy(A::getId, Reducer.reduce(
(cur) -> new A(cur.getId(), cur.getName(), new ArrayList<>(cur.getList())),
(fst, snd) -> fst.getList().addAll(snd.getList())
))).values();
或者如果你有一个初始化集合的空构造函数
Or like if you have an empty constructor that initializes the collections
Collection<A> result = list.stream()
.collect(Collectors.groupingBy(A::getId, Reducer.reduce(A::new,
(fst, snd) -> {
fst.getList().addAll(snd.getList());
fst.setId(snd.getId());
fst.setName(snd.getName());
}
))).values();
最后,如果你已经有其他答案中提到的复制构造函数或合并方法,你可以简化代码甚至更多或使用方法。
Finally, if you already have the copy constructor or the merge method mentioned in the other answers you can simplify the code even more or use the Collectors.toMap method.
这篇关于使用Java流合并列表中相同对象下的列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!