Java 8:通过关键字合并包含对象的两个列表

16

我有两个列表:

List<Server> servers1 = new ArrayList<>();
Server s1 = new Server("MyServer");
s1.setAttribute1("Attribute1");
servers1.add(s1);

List<Server> servers2 = new ArrayList<>();
Server s2 = new Server("MyServer");
s2.setAttribute2("Attribute2");
servers2.add(s2);

servers1包含具有nameattribute1(但没有attribute2)的服务器。
servers2包含具有nameattribute2(但没有attribute1)的服务器。

public class Server {
   private String name;
   private String attribute1;
   private String attribute2;

   public Server(String name) { 
       this.name = name;
       this.attribute1 = "";
       this.attribute2 = "";
   }

   //Getters & Setters

}

有没有人知道如何将这两个列表合并成一个列表,其中每个 Server 仅出现一次(按 name ),但具有两个属性?

有些服务器只存在于其中一个列表中。 最终列表应包含所有服务器。

<code><code>    List<Server> servers1 = new ArrayList<>();
    Server s1 = new Server("MyServer");
    s1.setAttribute1("Attribute1");

    Server s2 = new Server("MyServer2");
    s2.setAttribute1("Attribute1.2");

    servers1.add(s1);
    servers1.add(s2);

    List<Server> servers2 = new ArrayList<>();
    Server s3 = new Server("MyServer");
    s3.setAttribute2("Attribute2");

    Server s4 = new Server("MyServer3");
    s4.setAttribute2("Attribute2.2");

    servers2.add(s3);
    servers2.add(s4);
</code></code>

应该得到的结果是:

[服务器 [名称=MyServer,属性1=Attribute1,属性2=Attribute2],

服务器 [名称=MyServer2,属性1=Attribute1.2,属性2=]]

服务器 [名称=MyServer3,属性1=,属性2=Attribute2.2]]

//解决方案(感谢大家的帮助!)

Map<String, Server> serverMap1 = Stream.concat(servers1.stream(), servers2.stream())
            .collect(Collectors.toMap(Server::getName, Function.identity(), 
            (server1, server2) -> {
                server1.setAttribute2(server2.getAttribute2()); 
                return server1; 
            }));

servers1创建一个从nameServer的映射表,然后通过名称在地图中查找服务器,将缺失的attribute2填充为来自servers2的值。尝试自己编写一些代码。请见以下示例代码: - lexicore
如果这是你的整个“Server”类,那么“List”中的实体如何具有“name”,“attribute1”或“attribute2”的任何值。如果这不是整个类,请包括构造函数。 - CraigR8806
这两个列表中是否始终包含相同的服务器,或者存在不会合并的服务器? - Marco Stramezzi
好问题。有一些服务器不会被合并。 最终,我需要所有的服务器,如果它们可以合并,就应该合并。 - Nibor
我尝试了同样的事情。我没有得到第二个列表中不存在于第一个列表中对象的数据。在这种情况下是针对server3的。 - Rushi M Thakker
3个回答

4
将每个列表转换为映射并合并它(我使用Lombok来避免编写样板代码):
@Data
@NoArgsConstructor
@AllArgsConstructor
class Server {
    private String name;
    private String attribute1;
    private String attribute2;
}

public class ServerMain {

    public static void main(String[] args) {

        List<Server> servers1 = Arrays.asList(
                new Server("name1", "attr1.1", null),
                new Server("name2", "attr1.2", null));

        List<Server> servers2 = Arrays.asList(
                new Server("name1", null, "attr2.1"),
                new Server("name2", null, "attr2.2"));

        Map<String, Server> serverMap1 = servers1.stream().collect(Collectors.toMap(Server::getName, Function.identity()));
        Map<String, Server> serverMap2 = servers2.stream().collect(Collectors.toMap(Server::getName, Function.identity()));

        serverMap1.keySet().forEach(key -> serverMap1.merge(key,
                serverMap2.get(key),
                (server1, server2) -> {
                    server1.setAttribute2(server2.getAttribute2());
                    return server1;
                }));


        System.out.println(serverMap1);
    }
}

1
你正在做太多的工作,将数据收集到两个映射中,每个服务器需要进行两次映射查找。你可以完全删除serverMap2,只需执行servers2.forEach(s -> serverMap1.merge(s.getName(), s, /*与之前相同的合并函数*/));即可。 - Holger
6
如果你确信键是唯一的,那么你可以在单个Stream操作中完成它:serverMap1 = Stream.concat(servers1.stream(), servers2.stream()) .collect(Collectors.toMap(Server::getName, Function.identity(), /* 与之前相同的合并函数 */)); - Holger
@Holger 我建议您将您的评论转换为答案,它看起来像是一个“优雅”的基于流的答案。 - Artur Biesiadowski
不在servers1中但存在的服务器也应该出现在最终列表中(即使它只包含一个属性)。 - Nibor
@Holger @DmitryGorkovets,这对我不起作用。Exception in thread "main" java.lang.IllegalStateException: Duplicate key,因为该映射将名称作为键两次。 - Nibor
显示剩余2条评论

2

对于servers1中的每个服务器,找到一个在servers2中匹配的服务器,并将第一个服务器的缺失属性设置为正确的属性,否则,如果没有按名称匹配的服务器,则返回null。此外,请注意,此实现将仅返回包含在servers1中的服务器名称列表。

@AllArgsConstructor
@Data
private class Server {
   private String name;
   private String attribute1;
   private String attribute2;
}

List<Server> completServersInfo = servers1.stream()
     .map((server1 -> {
        Server matchingServer = servers2.stream()
           .filter(server2 -> server2.name.equals(server1.name))
           .findFirst()
           .orElse(null);
        server1.setAttribute2(matchingServer.attribute2);
        return server1;
     }))
     .collect(Collectors.toList());

如果你有信心两个列表中有相同的键存在,那么请执行以下程序相关内容的翻译:Map<String, Server> serverMap1 = servers1.stream().collect(Collectors.toMap(Server::getName, Function.identity()));servers2 .forEach(key->serverMap1.get(key.getName()).setAttribute2(key.getAttribute2()));System.out.println(serverMap1); - Sudarsana Kasireddy
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Nibor

2
也许你可以使用一个地图来做?地图的键将是服务器的名称,而地图的值则是对象 Server
Map<String, Server> map = new HashMap<>();
for (Server s : servers1) {
    map.put(s.getName(), s);
}
for (Server s : servers2) {
    String key = s.getName();
    if (map.containsKey(key)) {
        map.get(key).setAttribute2(s.getAttribute2());
    } else {
        map.put(key, s);
    }
}
List<Server> servers = new ArrayList<>(map.values()); // or other impl.

喜欢这种方法。即使处理数十万条记录,也非常高效。 - Zack Macomber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接