-1

I have three HashMap of this generic type HashMap<String,Server> but I want to combine the data of all the HashMap based on my unique key. But only Server pojo has different data.

So first hashmap has some servers information like ip_address and server name therefore I have both values in map and ip_address as key. Then I have some other hardware spec stored in other map and ip_address as key and so same in third map.

Therefore, combining all POJO based on key I will get complete server information with corresponding ip_address.

I don't know how to do it without doing nested operation

Kramer
  • 389
  • 8
  • 34
  • Possible duplicate of [How can I combine two HashMap objects containing the same types?](https://stackoverflow.com/questions/4299728/how-can-i-combine-two-hashmap-objects-containing-the-same-types) – Logan Aug 06 '18 at 04:55
  • @Logan logan if I put map1 value in map2 and they have the same key it will replace the map2 new Server with only hardware specs. Server is a POJO – Kramer Aug 06 '18 at 04:57
  • 1
    Can you give some examples? – zhh Aug 06 '18 at 05:09
  • Concat steams of each entrySet and then carry out a merging reduction using groupingBy. – Boris the Spider Aug 06 '18 at 06:10

4 Answers4

0

Use putAll():

Map<String, Server> all = new HashMap<>();
all.putAll(map1);
all.putAll(map2);
all.putAll(map3);

If you want a one-liner:

Map<String, Server> all = Stream.of(map1, map2, map3)
    .reduce(new HashMap<>(), (a, b) -> {a.putAll(b); return a;});

Collisions result in replacement.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • From my reading of the answer, the OP wants to merge the values in each map. – Boris the Spider Aug 06 '18 at 06:09
  • @boris how do you merge a Server? I have assumed the last server encountered for a given key is the winner. – Bohemian Aug 06 '18 at 06:16
  • The OP talks about the first map having some information and other maps having other information. I suppose they're all partially populated. As to how specially one merges a server - that's probably one for the OP; if they're just beans then I suppose a whole mess of `if(a.getCCC!= null) b.setXXX()` type operations... – Boris the Spider Aug 06 '18 at 06:20
  • If you are calling `reduce` on `Stream` or using the reducing `Collector` I believe that the value must be immutable and the reduction associative - this relates to how `Stream` parallelises the reduction. – Boris the Spider Aug 06 '18 at 06:53
0

Here is a way to do this. I have shown the example code using only two Map collections, and, the third can be applied in similar way.

I am using Map's computeIfPresent method to "merge" the values of the two maps. The BiFunction (the argument for the computeIfPresent) need to take care of the "merging" aspect - any validations, etc., presently not known (to me). Also, see the note below on computeIfPresent.

Example code:

import java.util.*;
import java.util.function.*; 

public class CombiningMaps {

    private static Map<String, ServerInfo> map1 = new HashMap<>();
    private static Map<String, ServerInfo> map2 = new HashMap<>();

    private static BiFunction<String, ServerInfo, ServerInfo> combineWithMap2 = 
        (k, v) -> {
                    if (map2.get(k) != null) {
                        ServerInfo v2 = map2.get(k);
                        // combine values of map1 and map2
                        v.setHw2(v2.getHw2());
                    }
                    return v;
                };

    public static void main(String [] args) {

        // Add some test data to map 1
        map1.put("serv1", new ServerInfo("0A", "a1", "",""));
        map1.put("serv2", new ServerInfo("0B", "b1", "",""));
        map1.put("serv3", new ServerInfo("0C", "c1", "",""));
        System.out.println(map1);

        // Add some data to map 2
        map2.put("serv1", new ServerInfo("0A", "", "a2",""));
        map2.put("serv2", new ServerInfo("0B", "", "b2",""));
        map2.put("serv3", new ServerInfo("0C", "", "c2",""));
        System.out.println(map2);

        // Update map1 with map 2's info
        map1.forEach((k,v) -> map1.computeIfPresent(k, combineWithMap2));
        System.out.println(map1);
    }
}

class ServerInfo {
    private String ipAddr;
    private String hw1; // hw stands for hardware related info
    private String hw2;
    private String hw3;
    public ServerInfo(String ipAddr, String hw1, String hw2, String hw3) {
        this.ipAddr = ipAddr;
        this.hw1 = hw1;
        this.hw2 = hw2;
        this.hw3 = hw3;
    }
    public String getIpAddr() {
        return ipAddr;
    }
    public String getHw1() {
        return hw1;
    }
    public void setHw1(String s) {
        hw1 = s;
    }
    public String getHw2() {
        return hw2;
    }
    public void setHw2(String s) {
        hw2 = s;
    }
    public String getHw3() {
        return hw3;
    }
    public void setHw3(String s) {
        hw3 = s;
    }
    public String toString() {
        return ipAddr + ":" + hw1 + "-" + hw2 + "-" +  hw3;
    }
}


The output:

{serv2=0B:b1--, serv3=0C:c1--, serv1=0A:a1--}
{serv2=0B:-b2-, serv3=0C:-c2-, serv1=0A:-a2-}
{serv2=0B:b1-b2-, serv3=0C:c1-c2-, serv1=0A:a1-a2-}


How the computeIfPresent behaves (some scenarios):

Consider a Map<String, Integer> map with keys and values: {four=4, one=1, ten=10, two=2, three=3, five=5, eleven=11, twelve=null}

(1) updates the mapping with new value (note the lambda is a BiFunction returning a newly computed value):

map.computeIfPresent("ten", (k, v) -> new Integer(100));

(2) the function returns a null, the existing mapping is removed:

map.computeIfPresent("eleven", (k, v) -> null);

(3) the mapping is not added, as there is no existing mapping:

map.computeIfPresent("twenty", (k, v) -> new Integer(20));

(4) the existing value is null, so there is no change:

map.computeIfPresent("twelve", (k, v) -> new Integer(12));
prasad_
  • 12,755
  • 2
  • 24
  • 36
  • The result is not yielded and also when I run its giving values of map1 only please look I did it and posting the answer – Kramer Aug 06 '18 at 07:11
  • I don't follow your comment. – prasad_ Aug 06 '18 at 07:14
  • Ok your approach works I was making a little mistake but what will happen if I want the third map to merge? Since in Bifunction it might override if you set fields for third map can we have another Bifunction – Kramer Aug 06 '18 at 07:18
  • 1
    Do this to add the third map: `map1.forEach((k,v) -> map1.computeIfPresent(k, combineWithMap3));` The `combineWithMap3` is a new `BiFunction` and uses the `map3`. – prasad_ Aug 06 '18 at 07:22
  • Thanks for accepting the answer. Only you can decide which one to use. Consider one from the point of clarity and maintainability. Also, one can improvise on these solutions. – prasad_ Aug 06 '18 at 07:24
  • `map1.forEach((k,v) -> map1.computeIfPresent` - mutation of a collection in its own `forEach` is not allowed. – Boris the Spider Aug 07 '18 at 06:50
  • See this: [How to replace HashMap Values while iterating over them in Java](https://stackoverflow.com/questions/10993403/how-to-replace-hashmap-values-while-iterating-over-them-in-java) -and- this: [Map#forEach](https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#forEach-java.util.function.BiConsumer-). – prasad_ Aug 07 '18 at 07:19
0

After getting all values in three maps I used finalMap to combine values. Here is the working for it since the key is same in all map therefore getting key of map using key of map1 was a good idea.

Set<Map.Entry<String, Server>> set1 = map.entrySet();  
                    for (Map.Entry<String, Server> me : set1) {
                        Server server=new Server();
                                     server.setIp_Address(me.getKey());
                                     server.setServerName(me.getValue().getServerName());
                                     server.setOsName(map1.get(me.getKey()).getOsName());
                                     server.setOsVersion(map1.get(me.getKey()).getOsVersion());
                                     server.setOsArchitecture(map1.get(me.getKey()).getOsArchitecture());
                                     server.setHardDiskCapacity(map1.get(me.getKey()).getHardDiskCapacity());
                                     server.setRamCapacity(map1.get(me.getKey()).getRamCapacity());
                                     server.setAvgNetWorkUtilizationSent(map2.get(me.getKey()).getAvgNetWorkUtilizationSent());
                                     server.setAvgNetworkUtilizationReceived(map2.get(me.getKey()).getAvgNetworkUtilizationReceived());
                                     server.setAvgCPUtilization(map2.get(me.getKey()).getAvgCPUtilization());
                                     server.setAvgRamUtilization(map2.get(me.getKey()).getAvgRamUtilization());
                                     finalMap.put(me.getKey(), server);
                   }
                    Set<Map.Entry<String, Server>> set2 = finalMap.entrySet();  
                    for (Map.Entry<String, Server> me : set2) {
            System.out.println(" ServerIP : "+ me.getValue().getIp_Address()+"\t"+" Server Name :"+me.getValue().getServerName()+"\t \t"+" Hardware Capacity :"+me.getValue().getHardDiskCapacity()+"\t"+" Average CPU Utlization: "+me.getValue().getAvgCPUtilization());
                    }
Kramer
  • 389
  • 8
  • 34
0

Your problem it to merge two POJO classes. For example

class Server {

    private String ipAddr;
    private String hw1;
    private String hw2;
    private String hw3;
    //Getter and Setters
}

Server s1 = new Server("0A", "a1", null, null);
Server s2 = new Server("0A", null, "b2", null);

So the merged pojo should be like this.

Server merged = merge(s1, s2);// Server{ipAddr=0A, hw1=a1, hw2=b2, hw3=null}

The merge function looks like this...

public static Server merge(Server s1, Server s2) throws Exception {

    Server merged = new Server();

    for (Field field : Server.class.getDeclaredFields()) {
        field.setAccessible(true);
        Object getS1 = field.get(s1);
        Object getS2 = field.get(s2);

        if(getS1 == null && getS2 != null) {
            field.set(merged, getS2);
        } else if (getS1 != null && getS2 == null) {
            field.set(merged, getS1);
        } else {  //equal values
            field.set(merged, getS1);
        }
    }

    return merged;
}

Here is the example code to merge three maps, Its kinda quick and dirty but works well.

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

class MergeMaps {

public static void main(String[] args) throws Exception {

    Map<String, Server> map1 = new HashMap<>();
    Map<String, Server> map2 = new HashMap<>();
    Map<String, Server> map3 = new HashMap<>();

    // Add some test data to map 1
    map1.put("serv1", new Server("0A", "a1", null, null));
    map1.put("serv2", new Server("0B", "b1", null, null));
    System.out.println(map1);

    // Add some data to map 2
    map2.put("serv1", new Server("0A", null, "a2", null));
    map2.put("serv2", new Server("0B", null, "b2", null));
    map2.put("serv3", new Server("0C", null, "c2", null));
    System.out.println(map2);

    // Add some data to map 3
    map3.put("serv1", new Server("0A", null, null, "a3"));
    map3.put("serv2", new Server("0B", null, null, "b3"));
    map3.put("serv3", new Server("0C", null, null, "c3"));
    map3.put("serv4", new Server("0D", null, null, "d4"));
    System.out.println(map3);

    Map<String, Server> resultingMap = new HashMap<>();

    resultingMap.putAll(map1);

    for (Map.Entry<String, Server> entry : map2.entrySet()) {
        if (resultingMap.containsKey(entry.getKey())) {
            Server s = resultingMap.get(entry.getKey());
            Server t = entry.getValue();

            Server merged = merge(s, t);
            resultingMap.put(entry.getKey(), merged);
        } else {
            resultingMap.put(entry.getKey(), entry.getValue());
        }
    }

    for (Map.Entry<String, Server> entry : map3.entrySet()) {
        if (resultingMap.containsKey(entry.getKey())) {
            Server server1 = resultingMap.get(entry.getKey());
            Server server2 = entry.getValue();

            Server merged = merge(server1, server2);
            resultingMap.put(entry.getKey(), merged);
        } else {
            resultingMap.put(entry.getKey(), entry.getValue());
        }
    }
    System.out.println(resultingMap);
}

public static Server merge(Server s1, Server s2) throws Exception {

    Server merged = new Server();

    for (Field field : Server.class.getDeclaredFields()) {
        field.setAccessible(true);
        Object getS1 = field.get(s1);
        Object getS2 = field.get(s2);

        if (getS1 == null && getS2 != null) {
            field.set(merged, getS2);
        } else if (getS1 != null && getS2 == null) {
            field.set(merged, getS1);
        } else {
            field.set(merged, getS1);
        }
    }

    return merged;
}
}

class Server {

    private String ipAddr;
    private String hw1;
    private String hw2;
    private String hw3;

    public Server() {
    }

    public Server(String ipAddr, String hw1, String hw2, String hw3) {
        this.ipAddr = ipAddr;
        this.hw1 = hw1;
        this.hw2 = hw2;
        this.hw3 = hw3;
    }
    //Getter and setters
    @Override
    public String toString() {
        return "Server{" + "ipAddr=" + ipAddr + ", hw1=" + hw1 + ", hw2=" + hw2 +     ", hw3=" + hw3 + '}';
}
}

The output looks like this..

{serv2=Server{ipAddr=0B, hw1=b1, hw2=null, hw3=null}, serv1=Server{ipAddr=0A, hw1=a1, hw2=null, hw3=null}}
{serv2=Server{ipAddr=0B, hw1=null, hw2=b2, hw3=null}, serv3=Server{ipAddr=0C, hw1=null, hw2=c2, hw3=null}, serv1=Server{ipAddr=0A, hw1=null, hw2=a2, hw3=null}}
{serv2=Server{ipAddr=0B, hw1=null, hw2=null, hw3=b3}, serv3=Server{ipAddr=0C, hw1=null, hw2=null, hw3=c3}, serv4=Server{ipAddr=0D, hw1=null, hw2=null, hw3=d4}, serv1=Server{ipAddr=0A, hw1=null, hw2=null, hw3=a3}}
{serv2=Server{ipAddr=0B, hw1=b1, hw2=b2, hw3=b3}, serv3=Server{ipAddr=0C, hw1=null, hw2=c2, hw3=c3}, serv4=Server{ipAddr=0D, hw1=null, hw2=null, hw3=d4}, serv1=Server{ipAddr=0A, hw1=a1, hw2=a2, hw3=a3}}
efex09
  • 417
  • 2
  • 12
  • This assumes that the fields in a POJO are `public`. They are not. – Boris the Spider Aug 07 '18 at 06:51
  • @BoristheSpider Thanks for pointing this out. I have updated code to merge private fields as well. – efex09 Aug 07 '18 at 07:34
  • I don't think bypassing accessors to poke `private` fields directly really solves the problem. My suggestion would be to remove this homebrew and use a [known working solution](https://docs.oracle.com/javase/10/docs/api/java/beans/Introspector.html). – Boris the Spider Aug 07 '18 at 08:20