问题
I uttlerly convinced that my question its quite simple but im unable to do it with streams (if there is a way to do it without stream will be helpfull too) Suppose that we have this list of users
public class Users {
String firstName;
String lastName;
double accountBalance;
String type;
String extraField;
}
and suppose that we have the following data in my List < Users >
"Users": [{
"firstName": "Scott",
"lastName": "Salisbury",
"accountBalance": "100",
"type" : "A"
}, {
"firstName": "John",
"lastName": "Richards",
"accountBalance": "200",
"type" :"C"
}, {
"firstName": "John",
"lastName": "Richards",
"accountBalance": "200",
"type " : "C",
"ExtraField": "Apply"
}]
the expected result here its given that firstName, lastName and type appears twice on the list just merge the results that are common without missing any field
Expected output
"Users": [{
"firstName": "Scott",
"lastName": "Salisbury",
"accountBalance": "100",
"type" : "A"
}, {
"firstName": "John",
"lastName": "Richards",
"accountBalance": "400",//merged values
"type " : "C",
"ExtraField": "Apply" //value that remains in one object of the list
}]
回答1:
You can create a key class containing the three fields, like
@Data
class UserKey {
String firstName;
String lastName;
String type;
static UserKey from(User user) { /* TODO (trivial) */ }
}
groupingBy
Those can be used to group your users
Map<UserKey,List<User>> grouped =
users.stream().collect(Collectors.groupingBy(UserKey::from));
Each of these lists can then be merged by
Optional<User> summed = userList.stream()
.collect(Collectors.reducing((u1, u2) -> {
u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
});
This can also be given directly as a downstream collector to the groupingBy
:
Map<UserKey,Optional<User>> mergedMap =
users.stream().collect(Collectors.groupingBy(UserKey::from,
Collectors.reducing((u1, u2) -> {
u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
return u1;
}));
Since those Optional
s are guaranteed to be filled, you can just call get()
on them; also, you don't need the keys anymore, so
List<User> result = mergedMap.values().stream()
.map(Optional::get)
.collect(toList());
toMap
As Naman suggested in the comments, you can also shortcut this by toMap
.
Map<UserKey,User> mergedMap = users.stream()
.collect(toMap(UserKey::from, Function.identity(),
(u1, u2) -> {
u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
return u1;
}));
List<User> result = new ArrayList<>(mergedMap.values());
Note that the reducing function has the side effect of manipulating one of the original user objects in the list, so make sure you don't need them again.
回答2:
If the data is just Lists of Objects, then you should be able to merge the data fairly straight forward with a few loops. For example:
public static ArrayList<User> mergeData(ArrayList<User> userList) {
ArrayList<User> users = new ArrayList<>(userList);
for(int i = 0; i < users.size(); i++) {
User currentUser = users.get(i);
for(int j = i + 1; j < users.size(); j++) {
User otherUser = users.get(j);
if(currentUser.firstName.equals(otherUser.firstName)
&& currentUser.lastName.equals(otherUser.lastName)
&& currentUser.type.equals(otherUser.type)) {
//Apply the field merging
currentUser.accountBalance += otherUser.accountBalance;
if(currentUser.extraField == null) {
currentUser.extraField = otherUser.extraField;
} else {
//Handle case where you pick whether to keep the value or not.
}
//Remove the merged data and move index back to account for removal
users.remove(j);
j--;
}
}
}
return users;
}
The code just runs through the values, compares it against every other remaining value in the list, merges where applicable, and then removes the merged object from the rest of the list before moving on to the next value.
来源:https://stackoverflow.com/questions/60415626/java-merge-objects-of-list-given-a-condition