Java 8多键分组

Java 8多键分组,java,hashmap,java-stream,collectors,multikey,Java,Hashmap,Java Stream,Collectors,Multikey,我正在寻找一些帮助,从一个包含多个键的列表中对对象列表进行分组 基本上,我有一个用户列表,其中包含他们的订单列表,我希望能够使用用户名和地址作为键将他们分组在一起 示例数据: <UserList> <User> <Username>user123</Username> <Address>London</Address> <Trans

我正在寻找一些帮助,从一个包含多个键的列表中对对象列表进行分组

基本上,我有一个用户列表,其中包含他们的订单列表,我希望能够使用用户名和地址作为键将他们分组在一起

示例数据:

<UserList>
        <User>
            <Username>user123</Username>
            <Address>London</Address>
            <TransactionList>
                <TransactionList>
                    <OrderNumber>1</OrderNumber>
                    <Cost>3683446.6600</Cost>
                </TransactionList>
            </TransactionList>
        </User>
              <User>
            <Username>user123</Username>
            <Address>London</Address>
            <TransactionList>
                <TransactionList>
                    <OrderNumber>3</OrderNumber>
                    <Cost>500</Cost>
                </TransactionList>
            </TransactionList>
        </User>
               <User>
            <Username>user12356</Username>
            <Address>Manchester</Address>
            <TransactionList>
                <TransactionList>
                    <OrderNumber>6</OrderNumber>
                    <Cost>90000</Cost>
                </TransactionList>
            </TransactionList>
        </User>
              <User>
            <Username>user12356</Username>
            <Address>Manchester</Address>
            <TransactionList>
                <TransactionList>
                    <OrderNumber>10</OrderNumber>
                    <Cost>100</Cost>
                </TransactionList>
            </TransactionList>
        </User>
    </UserList>

用户123
伦敦
1.
3683446.6600
用户123
伦敦
3.
500
用户12356
曼彻斯特
6.
90000
用户12356
曼彻斯特
10
100
我想这样排序,以便每个用户只有一个实例,并且其相关事务根据用户名和地址分组到一个列表中:

<UserList>
        <User>
            <Username>user123</Username>
            <Address>London</Address>
            <TransactionList>
                <TransactionList>
                    <OrderNumber>1</OrderNumber>
                    <Cost>3683446.6600</Cost>
                </TransactionList>
                <TransactionList>
                    <OrderNumber>3</OrderNumber>
                    <Cost>500</Cost>
                </TransactionList>
            </TransactionList>
        </User>
         <User>
            <Username>user12356</Username>
            <Address>Manchester</Address>
            <TransactionList>
                <TransactionList>
                    <OrderNumber>6</OrderNumber>
                    <Cost>90000</Cost>
                </TransactionList>
                 <TransactionList>
                    <OrderNumber>10</OrderNumber>
                    <Cost>100</Cost>
                </TransactionList>
            </TransactionList>
        </User>
    </UserList> 
        

用户123
伦敦
1.
3683446.6600
3.
500
用户12356
曼彻斯特
6.
90000
10
100
我曾尝试使用地图进行此操作:

Map<String, Map<String,List<User>>> map;

map = userLists.getUserList().stream()
                .collect(Collectors.groupingBy(User::getUserName, Collectors.groupingBy(User::getAddress)));
Map;
map=userLists.getUserList().stream()
.collect(Collectors.groupingBy(User::getUserName,Collectors.groupingBy(User::getAddress)));
这正是我想要做的,但我想知道是否有更好的方法使用多键映射或类似的东西,然后迭代并在键匹配的情况下放入事务列表


提前感谢您的帮助。

您的方法没有错,第一步是正确的:按
用户名
对用户进行分组,然后按
地址
对用户进行分组。就我个人而言,我倒过来说,因为很可能有更多的用户使用相同的地址,但顺序对于获得正确的结果并不重要

我注意到预期的输出看起来像
列表
,减少了具有共同特征(
用户名
地址
)的用户,并连接了
事务列表
列表。虽然带有复合键的映射(例如,
${userName}{u${address}
)可能会有所帮助,但我还是选择
列表,它符合预期的输出

因此,第二步是迭代所有这些条目,并将
映射中的
列表
缩减为单个用户。每个内部条目将与单个用户关联(因为
用户名
地址
)。因为每一个都非常适合这项任务。好了:

Map<String, Map<String,List<User>>> map = userLists.stream()
     .collect(Collectors.groupingBy(
            User::getUserName, 
            Collectors.groupingBy(User::getAddress)));

List<User> list = new ArrayList<>();                 // stored output

map.forEach((userName, groupedByAddress) -> {        // for each 'userName'
    groupedByAddress.forEach((address, users) -> {   // ... and each 'address'
        User userToAdd = new User();                 // ...... create an 'User'
        userToAdd.setUserName(userName);             // ...... with the 'userName'  
        userToAdd.setAddress(address);               // ...... with the 'address'
        users.forEach(user -> {                      // ...... and of each the user's
            userToAdd.getTransactionList()           // .......... concatenate
                .addAll(user.getTransactionList());  // .......... all the transactions
        });
    });
});
Map Map=userLists.stream()
.collect(收集器.groupingBy(
User::getUserName,
Collectors.groupingBy(User::getAddress));
List List=新建ArrayList();//存储输出
map.forEach((用户名,groupedByAddress)->{//对于每个“用户名”
groupedByAddress.forEach((地址,用户)->{/…和每个“地址”
User userToAdd=new User();/…创建一个“用户”
userToAdd.setUserName(userName);/…和“userName”
userToAdd.setAddress(address);/…和“address”
users.forEach(user->{//…和每个用户的
userToAdd.getTransactionList()/….连接
.addAll(user.getTransactionList());/…所有事务
});
});
});

流API是一种很好的分组方法,可以获得中间结果,但是,对于这种进一步的缩减,它将非常笨拙。

您所寻找的实际上是一种基于普通用户的名称和地址的合并功能。如果您查看
Collectors.toMap
API,它为您提供了类似的功能以及
Map
的键和值选择

Collectors.toMap(
        u -> Arrays.asList(u.getUserName(), u.getAddress()),
        Function.identity(),
        User::mergeUsers
));
合并的位置看起来就像您所期望的那样

static User mergeUsers(User user1, User user2) {
    List<Transaction> overall = new ArrayList<>(user1.getTransactionList());
    overall.addAll(user2.getTransactionList());
    return new User(user1.getUserName(), user1.getAddress(), overall);
}

如果有数百名用户,这会起作用吗?是的,会。什么让你怀疑呢?你正在将用户user1和用户user2传递到merge方法中,并特别指出要添加哪个用户事务。是的,对于具有相同用户名和地址的用户,将调用merge函数。因此,事务将全部添加到从合并功能创建的最终用户实例中。非常感谢您的响应。这真的很有用。在添加到事务之前,我必须稍微更新您的代码以创建事务实例,因为我在.allAll(user.getTransaaActionList());;处得到了一个空指针;。如果我有3把钥匙,这也行吗?比如说用户名。第1行地址,城市?是的,原则相同。唯一的区别是
收集器.groupingBy
中的另一个下游,每个循环多嵌套一个。别忘了正确的“分层”:首先是城市,然后是地址,然后是用户名……非常感谢,我们将尝试一下!快乐编码:)
Collection<User> users = userLists.getUserList().stream()
        .collect(Collectors.toMap(
                u -> Arrays.asList(u.getUserName(), u.getAddress()),
                Function.identity(),
                User::mergeUsers
        )).values();