我有一个像这样的POJO:
public class Account {
private Integer accountId;
private List<String> contacts;
}equals和hashCode方法被设置为使用accountId字段来标识唯一性,因此任何具有相同accountId的帐户都是相等的,而不管contacts包含什么。
我有一个帐户列表,有一些重复与相同的accountId。如何使用Java8StreamAPI将这些副本合并在一起?
例如,帐户列表包含:
+-----------+----------+
| accountId | contacts |
+-----------+----------+
| 1 | {"John"} |
| 1 | {"Fred"} |
| 2 | {"Mary"} |
+-----------+----------+我希望它能提供这样的账户清单:
+-----------+------------------+
| accountId | contacts |
+-----------+------------------+
| 1 | {"John", "Fred"} |
| 2 | {"Mary"} |
+-----------+------------------+发布于 2018-01-24 02:46:53
使用Collectors.toMap参考文献:https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toMap-java.util.function.Function-java.util.function.Function-java.util.function.BinaryOperator-
@lombok.Value
class Account {
Integer accountId;
List<String> contacts;
}
List<Account> accounts = new ArrayList<>();
//Fill
List<Account> result = new ArrayList<>(accounts.stream()
.collect(
Collectors.toMap(Account::getAccountId, Function.identity(), (Account account1, Account account2) -> {
account1.getContacts().addAll(account2.getContacts());
account2.getContacts().clear();
return account1;
})
)
.values());发布于 2018-01-24 11:26:57
干净的Stream解决方案可以是安静的、复杂的,所以您最好使用一个集合API解决方案,该解决方案需要遵守的约束较少。
HashMap<Integer, Account> tmp = new HashMap<>();
listOfAccounts.removeIf(a -> a != tmp.merge(a.getAccountId(), a, (o,n) -> {
o.getContacts().addAll(n.getContacts());
return o;
}));在将它们的联系人添加到该id的第一个帐户后,这将直接从列表中删除所有具有重复id的元素。
当然,这假设列表支持删除,getContacts()返回的列表是对存储列表的引用,并支持添加元素。
解决方案是围绕Map.merge构建的,如果键不存在,它将添加指定的对象;如果键已经存在,则计算合并函数。合并函数在添加联系人之后返回旧对象,因此我们可以进行引用比较(a != …)来确定应该删除的副本。
发布于 2018-01-24 04:01:14
您可以将两个构造函数和一个merge方法添加到将组合联系人的Account类中:
public class Account {
private final Integer accountId;
private List<String> contacts = new ArrayList<>();
public Account(Integer accountId) {
this.accountId = accountId;
}
// Copy constructor
public Account(Account another) {
this.accountId = another.accountId;
this.contacts = new ArrayList<>(another.contacts);
}
public Account merge(Account another) {
this.contacts.addAll(another.contacts);
return this;
}
// TODO getters and setters
}那么,你有几个选择。一种是使用Collectors.toMap将帐户收集到地图上,按accountId进行分组,并通过Account.merge方法将帐户的联系人与相等的accountId合并。最后,获取映射的值:
Collection<Account> result = accounts.stream()
.collect(Collectors.toMap(
Account::getAccountId, // group by accountId (keys)
Account::new, // use copy constructor (values)
Account::merge)) // merge values with equal key
.values();您需要对值使用复制构造函数,否则在调用Account.merge时会更改原始列表的帐户。
一种类似的方法(没有流)是使用Map.merge方法:
Map<Integer, Account> map = new HashMap<>();
accounts.forEach(a ->
map.merge(a.getAccountId(), new Account(a), Account::merge));
Collection<Account> result = map.values();同样,您需要使用复制构造函数来避免原始列表帐户上的不需要的突变。
第三个更优化的选项(因为它没有为列表中的每个元素创建一个新帐户)包括使用Map.computeIfAbsent方法:
Map<Integer, Account> map = new HashMap<>();
accounts.forEach(a -> map.computeIfAbsent(
a.getAccountId(), // group by accountId (keys)
Account::new) // invoke new Account(accountId) if absent
.merge(a)); // merge account's contacts
Collection<Account> result = map.values();上面的所有选项都返回一个Collection<Account>。如果您需要一个List<Account>,您可以这样做:
List<Account> list = new ArrayList<>(result);https://stackoverflow.com/questions/48413789
复制相似问题