【问题标题】:Java - Merge objects of list given a conditionJava - 在给定条件下合并列表对象
【发布时间】:2020-06-10 10:15:00
【问题描述】:

我完全相信我的问题很简单,但我无法用流来解决(如果有办法在没有流的情况下做到这一点也会有帮助) 假设我们有这个用户列表

public class Users {
   String firstName;
   String lastName;
   double accountBalance;
   String type;
   String extraField;
}

并假设我们的列表中有以下数据

"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"
        }]

这里的预期结果是,鉴于 firstName、lastName 和 type 在列表中出现两次,只需合并常见的结果而不会丢失任何字段
预期输出

"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
        }]  

【问题讨论】:

  • bean 中可以有多少个字段?
  • @GovindaSakare 这是一个例子,假设有 5 个字段
  • 您是否使用任何库来处理这些数据?例如,JSON-Simple?
  • 您可以先使用groupingBy 使用提取关键字段的函数对它们进行分组;这会给你一个按键的用户列表。然后,您可以通过提供下游收集器来合并它们。
  • @daniu 好的,那我们remember it again吧。

标签: java java-stream


【解决方案1】:

您可以创建一个包含三个字段的键类,例如

@Data
class UserKey {
    String firstName;
    String lastName;
    String type;

    static UserKey from(User user) { /* TODO (trivial) */ }
}

分组依据

这些可用于对您的用户进行分组

Map<UserKey,List<User>> grouped = 
    users.stream().collect(Collectors.groupingBy(UserKey::from));

这些列表中的每一个都可以被合并

Optional<User> summed = userList.stream()
    .collect(Collectors.reducing((u1, u2) -> {
        u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
    });

这也可以直接作为下游收集器提供给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;
        }));

既然那些Optionals 是保证被填满的,你可以在他们上面调用get();另外,你不再需要钥匙了,所以

List<User> result = mergedMap.values().stream()
                 .map(Optional::get)
                 .collect(toList());

toMap

正如 Naman 在 cmets 中建议的那样,您也可以通过 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());

请注意,reduce 函数具有操作列表中原始用户对象之一的副作用,因此请确保您不再需要它们。

【讨论】:

  • 这不是 Collectors.toMap() 吗?我得到了无法解决方法“身份”错误,知道吗??
  • 您不应该使用方法引用。您应该使用 Function.identity()。如果您使用方法引用,则基本上是在尝试传递 Supplier>.
  • @Ramana 已更正,谢谢。我对 Collectors 方法使用并暗示静态导入。
【解决方案2】:

如果数据只是对象列表,那么您应该能够通过几个循环相当直接地合并数据。例如:

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;
}

代码只是遍历这些值,将其与列表中的所有其他剩余值进行比较,在适用的情况下进行合并,然后从列表的其余部分中删除合并的对象,然后再转到下一个值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 2021-11-13
    • 1970-01-01
    • 2019-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多