16

I have two lists:

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 contains servers with a name and an attribute1 (but no attribute2).
servers2 contains servers with a name and an attribute2 (but no 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

}

Does anyone know how I can merge these two lists to one list containing each Server only once (by name) but with both attributes?

There are servers which only exist in one or the other list. The final list should contain all servers.

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

should result in:

[Server [name=MyServer, attribute1=Attribute1, attribute2=Attribute2],

Server [name=MyServer2, attribute1=Attribute1.2, attribute2=]]

Server [name=MyServer3, attribute1=, attribute2=Attribute2.2]]

//SOLUTION (thx everybody for the help!)

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; 
            }));
5
  • Create a map of name to Server from servers1 and then fill the missing attribute2 from servers2 looking up the server in the map by name. Try to write some code on your own. Commented Jan 12, 2017 at 12:07
  • If that is the entirety of your Server class, how do the entities in either List have any values for name, attribute1 or attribute2. If this is not the entire class, please include constructor(s) Commented Jan 12, 2017 at 12:15
  • Does the two lists contain always the same servers or exist servers that will not be merged? Commented Jan 12, 2017 at 12:40
  • Good question. There are servers which won't be merged. In the end I need all servers and if they can be merged, the should. Commented Jan 12, 2017 at 12:46
  • I tried the same thing. I am not getting data for 2nd lists objects not present in 1st list. In this case for server3 Commented Oct 7, 2021 at 13:32

3 Answers 3

4

Convert each list to map and merge it (I use Lombok to not write boilerplate code):

@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);
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

You’re doing too much work, collecting into two maps to do two map lookups for each server. You can remove the serverMap2 entirely and just do servers2.forEach(s -> serverMap1.merge(s.getName(), s, /* the same merge function as before */));
If you are confident that the keys are unique, you can do it even in a single Stream operation: serverMap1 = Stream.concat(servers1.stream(), servers2.stream()) .collect(Collectors.toMap( Server::getName, Function.identity(), /* the same merge function as before */));
@Holger I would suggest converting your comment into answer, it looks like only 'elegant' stream based answer.
servers which don't exist in servers1 should be in the final list as well (even if it only contains one attribute then)
@Robin, see second comment of Holger
|
2

For each server in servers1 find a matching server2 in servers2 and set the missing attribute of the first one to correct attribute else if there is no matching server by name simply return null. Also note that this implementation will return only the list of servers name contained in 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());

2 Comments

if you're confident same key exists on both lists 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);
the same key doesn't need to exist in both lists. If it doesn't, the server should appear in the final list as it is (with missing arguments)
2

Maybe you can use a map to do it? The key of the map would be the name of the server and the value of the map is the object 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.

1 Comment

Love this approach. Very efficient even when processing hundreds of thousands of records.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.