我有一个简单的User
类,具有一个String
和一个int
属性。
我想以此方式添加两个用户列表:
赞:
List1: { [a:2], [b:3] }
List2: { [b:4], [c:5] }
ResultList: {[a:2], [b:7], [c:5]}
User
定义:
public class User {
private String name;
private int comments;
}
我的方法:
public List<User> addTwoList(List<User> first, List<User> sec) {
List<User> result = new ArrayList<>();
for (int i=0; i<first.size(); i++) {
Boolean bsin = false;
Boolean isin = false;
for (int j=0; j<sec.size(); j++) {
isin = false;
if (first.get(i).getName().equals(sec.get(j).getName())) {
int value= first.get(i).getComments() + sec.get(j).getComments();
result.add(new User(first.get(i).getName(), value));
isin = true;
bsin = true;
}
if (!isin) {result.add(sec.get(j));}
}
if (!bsin) {result.add(first.get(i));}
}
return result;
}
但是它将很多东西添加到列表中。
答案 0 :(得分:5)
最好通过toMap
收集器来完成此操作:
Collection<User> result = Stream
.concat(first.stream(), second.stream())
.collect(Collectors.toMap(
User::getName,
u -> new User(u.getName(), u.getComments()),
(l, r) -> {
l.setComments(l.getComments() + r.getComments());
return l;
}))
.values();
Stream<User>
将两个列表连接成一个Stream.concat
。toMap
收集器合并碰巧具有相同Name
的用户,并返回Collection<User>
的结果。如果您严格想要一个List<User>
,然后将结果传递到ArrayList
构造函数中,即List<User> resultSet = new ArrayList<>(result);
对@davidxxx表示敬意,您可以直接从管道中收集到列表,并避免使用以下方法创建中间变量:
List<User> result = Stream
.concat(first.stream(), second.stream())
.collect(Collectors.toMap(
User::getName,
u -> new User(u.getName(), u.getComments()),
(l, r) -> {
l.setComments(l.getComments() + r.getComments());
return l;
}))
.values()
.stream()
.collect(Collectors.toList());
答案 1 :(得分:3)
您必须使用中间映射来通过汇总年龄将两个列表中的用户合并。
一种方法是使用流,如Aomine's answer所示。这是没有流的另一种方法:
Map<String, Integer> map = new LinkedHashMap<>();
list1.forEach(u -> map.merge(u.getName(), u.getComments(), Integer::sum));
list2.forEach(u -> map.merge(u.getName(), u.getComments(), Integer::sum));
现在,您可以创建用户列表,如下所示:
List<User> result = new ArrayList<>();
map.forEach((name, comments) -> result.add(new User(name, comments)));
这假设User
的构造函数可以接受name
和comments
。
编辑:如@davidxxx所建议,我们可以通过剔除第一部分来改进代码:
BiConsumer<List<User>, Map<String, Integer>> action = (list, map) ->
list.forEach(u -> map.merge(u.getName(), u.getComments(), Integer::sum));
Map<String, Integer> map = new LinkedHashMap<>();
action.accept(list1, map);
action.accept(list2, map);
此重构将避免DRY。
答案 2 :(得分:2)
使用Collectors.groupingBy
和Collectors.reducing
的一种很直接的方法,不需要,需要使用setter,这是最大的优势,因为您可以保留User
不变:
Collection<Optional<User>> d = Stream
.of(first, second) // start with Stream<List<User>>
.flatMap(List::stream) // flatting to the Stream<User>
.collect(Collectors.groupingBy( // Collecting to Map<String, List<User>>
User::getName, // by name (the key)
// and reducing the list into a single User
Collectors.reducing((l, r) -> new User(l.getName(), l.getComments() + r.getComments()))))
.values(); // return values from Map<String, List<User>>
不幸的是,由于归约管道会返回Collection<Optional<User>>
,因此结果为Optional
,因为结果可能根本不存在。您可以流式传输值并使用map()
来摆脱Optional
或使用Collectors.collectAndThen
*:
Collection<User> d = Stream
.of(first, second) // start with Stream<List<User>>
.flatMap(List::stream) // flatting to the Stream<User>
.collect(Collectors.groupingBy( // Collecting to Map<String, List<User>>
User::getName, // by name (the key)
Collectors.collectingAndThen( // reduce the list into a single User
Collectors.reducing((l, r) -> new User(l.getName(), l.getComments() + r.getComments())),
Optional::get))) // and extract from the Optional
.values();
*感谢@ Aomine
答案 3 :(得分:2)
作为一种相当直接和有效的选择:
Map<String, Integer>
中,以将每个名称与评论总数(int
)或者,对于第三步,您可以使用Map
对collectingAndThen(groupingBy()..., m -> ...
收集器进行精加工转换
但是我并不总是很可读,在这里我们可以没有。
它将给出:
List<User> users =
Stream.concat(first.stream(), second.stream())
.collect(groupingBy(User::getName, summingInt(User::getComments)))
.entrySet()
.stream()
.map(e -> new User(e.getKey(), e.getValue()))
.collect(toList());