AndroidJava
AndroidJava

Reputation: 31

Copying HashMap to another HashMap

I am having a problem copying a HashMap A to HashMap B. B is always same as A. My idea is making a small tile game using HashMaps only.

Map<Point,Tile> A = new HashMap<Point,Tile>();

HashMap has 2 things. A point(key) and a tile object, which is another class I have made. Tile takes in two integers and a string. ( new Tile(x,y,string)). First two integers defines the point x and y and the string tells if its "OFF" or "ON".

What I do first is populate HashMap A with 2*2 elements.

for(int i=0; i<2;i++){
for(int j=0; j<2;j++){
Tile t = new Tile(i, j, "OFF");
A.put(new Point(i,j), t);
}
}

Then I copy HashMap A to HashMap B by adding A in constructor. My idea is so I can go back to default HashMap A by using HashMap B in the constructor(See later)

Map<Point,Tile> B = new HashMap<Point,Tile>(A);

Then I change tile (1,1) to "ON"

Tile t2 = A.get(new Point(1,1));
t2.setS("ON");

One of my tiles is "ON" now. Now I want to reset the board back to original(After the population stage). I clear HashMap A and make a new HashMap with HashMap B as the constructor.

A.clear();
A = new HashMap<Point,Tile>(B);

However, when I changed tile (1,1) to ON on HashMap A , it updated HashMap B as well. I thought making a new HashMap with a constructor will make a new copy of it, but doesn't seem to work.

The strange thing is that

Map<Point,String> A = new HashMap<Point,String>(); 

would work but not

Map<Point,Tile> A = new HashMap<Point,Tile>(); 

I want to somehow get the Original contents of HashMap A without me trying to loop over the elements again.

Here's my main class code

package main;

import java.awt.Point;
import java.util.HashMap;
import java.util.Map;

import model.Tile;

public class Test {

    public static void main(String[] args) {
    //list1
    Map<Point,Tile> A = new HashMap<Point,Tile>();

    //Populating map
    for(int i=0; i<2;i++){
        for(int j=0; j<2;j++){
            Tile t = new Tile(i, j, "OFF");
            A.put(new Point(i,j), t);
        }
    }

    //copying list1 to list2
    Map<Point,Tile> B = new HashMap<Point,Tile>(A);

    //Change tile on 1,1 to ON
    Tile t2 = A.get(new Point(1,1));
    t2.setS("ON");

    for(int i=0; i<2;i++){
        for(int j=0; j<2;j++){
            Tile tTemp = A.get(new Point(i,j));
            System.out.println(i+" "+j+" "+tTemp.getS());
        }
    }

    //Reseting tiles
    //clear first list
    A.clear();
    System.out.println("");
    //copy second list to first list
    A = new HashMap<Point,Tile>(B);


    for(int i=0; i<2;i++){
        for(int j=0; j<2;j++){
            Tile tTemp = A.get(new Point(i,j));
            System.out.println(i+" "+j+" "+tTemp.getS());
            }
        }

    }

}

Here's the tile class

package main;

public class Tile {

public int x,y;
public String s;

public Tile(int x1, int y1, String st){
    x=x1;
    y=y1;
    s=st;
}

public int getX() {
    return x;
}

public void setX(int x) {
    this.x = x;
}

public int getY() {
    return y;
}

public void setY(int y) {
    this.y = y;
}

public String getS() {
    return s;
}

public void setS(String s) {
    this.s = s;
}

}

Here's what is getting printed before clearing HashMap A

0 0 OFF
0 1 OFF
1 0 OFF
1 1 ON

Here's what is getting printed After clearing HashMap A and then copy B to it.

0 0 OFF
0 1 OFF
1 0 OFF
1 1 ON

No difference.

Upvotes: 2

Views: 3408

Answers (5)

Ravi K Thapliyal
Ravi K Thapliyal

Reputation: 51711

You need to deep copy your HashMap by cloning the Tile objects.

By default, the HashMap constructor is doing a shallow copy. It simply copies the values from the passed-in Map, which would work for primitives, Strings (and other immutable objects) but not for references to Object types as the copied reference would point to the same original object and hence modify it later.

So to fix this issue, just implement a deep copy method as

public Map<Point, Tile> getDeepCopy(Map<Point, Tile> source) {
    Map<Point, Tile> copy = new HashMap<Point, Tile>();
    for (Map.Entry<Point, Tile> entry : source.entrySet())
        copy.put(entry.getKey(), entry.getValue().clone());
    return copy;
}

and make your Tile class implement Cloneable and override the clone() method as

public class Tile implements Cloneable {

    // other implementation

    public Tile clone() throws CloneNotSupportedException {
        return (Tile) super.clone();
    }
}

The way you're using your Points, I didn't see a need to clone() them as well but if you want them to be deep-cloned as well, just modify it as Tile above.

Upvotes: 2

Om.
Om.

Reputation: 2682

Here use this utility class to perform deep cloning.

//copying list1 to list2
    Map<Point,Tile> B = DeepCopy.deepCopy(original)(A);

Utility Class:

public final class DeepClone {

    private DeepClone(){}

    public static <X> X deepClone(final X input) {
        if (input == null) {
            return input;
        } else if (input instanceof Map<?, ?>) {
            return (X) deepCloneMap((Map<?, ?>) input);
        } else if (input instanceof Collection<?>) {
            return (X) deepCloneCollection((Collection<?>) input);
        } else if (input instanceof Object[]) {
            return (X) deepCloneObjectArray((Object[]) input);
        } else if (input.getClass().isArray()) {
            return (X) clonePrimitiveArray((Object) input);
        }

        return input;
    }

    private static Object clonePrimitiveArray(final Object input) {
        final int length = Array.getLength(input);
        final Object copy = Array.newInstance(input.getClass().getComponentType(), length);
        // deep clone not necessary, primitives are immutable
        System.arraycopy(input, 0, copy, 0, length);
        return copy;
    }

    private static <E> E[] deepCloneObjectArray(final E[] input) {
        final E[] clone = (E[]) Array.newInstance(input.getClass().getComponentType(), input.length);
        for (int i = 0; i < input.length; i++) {
            clone[i] = deepClone(input[i]);
        }

        return clone;
    }

    private static <E> Collection<E> deepCloneCollection(final Collection<E> input) {
        Collection<E> clone;
        // this is of course far from comprehensive. extend this as needed
        if (input instanceof LinkedList<?>) {
            clone = new LinkedList<E>();
        } else if (input instanceof SortedSet<?>) {
            clone = new TreeSet<E>();
        } else if (input instanceof Set) {
            clone = new HashSet<E>();
        } else {
            clone = new ArrayList<E>();
        }

        for (E item : input) {
            clone.add(deepClone(item));
        }

        return clone;
    }

    private static <K, V> Map<K, V> deepCloneMap(final Map<K, V> map) {
        Map<K, V> clone;
        // this is of course far from comprehensive. extend this as needed
        if (map instanceof LinkedHashMap<?, ?>) {
            clone = new LinkedHashMap<K, V>();
        } else if (map instanceof TreeMap<?, ?>) {
            clone = new TreeMap<K, V>();
        } else {
            clone = new HashMap<K, V>();
        }

        for (Entry<K, V> entry : map.entrySet()) {
            clone.put(deepClone(entry.getKey()), deepClone(entry.getValue()));
        }

        return clone;
    }
}

Reference : Assigning Hashmap to Hashmap

Upvotes: 0

Eran
Eran

Reputation: 393801

Map<Point,Tile> B = new HashMap<Point,Tile>(A); makes a shallow copy of your A map. It doesn't create copies of the Point and Tile values. It uses the same references as the ones located in the original Map. Therefore, when you change a Tile in the B map, the same Tile is changed in the A map and vice versa.

Map<Point,String> A = new HashMap<Point,String>(); works because String is immutable, so unlike your Tile instances, you can't change a String's state.

To create a deep copy of A, you'll have to iterate over the entries of A, create a copy of each key and value (assuming both the Point key and the Tile value are mutable - if Point is not mutable, it's enough to create a copy of the Tile values), and put the copies in the B map.

Upvotes: 0

S. Pauk
S. Pauk

Reputation: 5318

Map<Point,Tile> B = new HashMap<Point,Tile>(A); 

makes a shallow copy while you need a deep copy. You have either to implement deep copy manually or use some library to do that for you. Like this one:

Java Deep-Cloning library

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500385

However, when I changed tile (1,1) to ON on HashMap A , it updated HashMap B as well.

When you write:

Tile t2 = A.get(new Point(1,1));
t2.setS("ON");

you're not changing either of the maps. The maps just have references to Point objects and Tile objects. They have the same references - copying the map doesn't clone the objects within them.

So when you change the contents of one of those objects, you'll see that change regardless of which map you use to navigate to the object.

To put it another way, consider this situation:

  • I write my address on two pieces of paper
  • I give one piece of paper to Charlie and one to Joe
  • Charlie uses the piece of paper to find my house and paints my front door red
  • Joe uses the piece of paper to find my house and observe the colour of the door

Joe will see a red front door, yes? It's exactly the same here.

If you make your Point and Tile classes immutable, this won't be a problem - because you wouldn't be able to change the contents of existing objects. At that point there's no particularly meaningful difference between copying a reference and cloning the object. You'd end up writing something like:

Point p = new Point(1, 1);
Tile t2 = A.get(p);
t2 = t2.withS("ON"); // This would return a reference to a new object
A.put(t2);

Upvotes: 0

Related Questions