将 HashMap 复制到另一个 HashMap

Copying HashMap to another HashMap

我在将 HashMap A 复制到 HashMap B 时遇到问题。B 始终与 A 相同。我的想法是仅使用 HashMap 制作一个小游戏。

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

HashMap 有两个东西。一个点(键)和一个瓦片对象,这是我制作的另一个class。 Tile 接受两个整数和一个字符串。 (新瓷砖(x,y,字符串))。前两个整数定义点 x 和 y,字符串表示它是 "OFF" 还是 "ON".

我首先做的是用 2*2 个元素填充 HashMap A。

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

然后我通过在构造函数中添加 A 将 HashMap A 复制到 HashMap B。我的想法是,我可以通过在构造函数中使用 HashMap B 返回到默认的 HashMap A(见下文)

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

然后我将图块 (1,1) 更改为 "ON"

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

我的一块瓷砖现在 "ON"。现在我想将板重置回原来的状态(在填充阶段之后)。我清除 HashMap A 并使用 HashMap B 作为构造函数创建一个新的 HashMap。

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

但是,当我将 HashMap A 上的 tile (1,1) 更改为 ON 时,它也会更新 HashMap B。我以为用构造函数创建一个新的 HashMap 会创建一个新的副本,但似乎不起作用。

奇怪的是

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

可以,但不能

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

我想以某种方式获取 HashMap A 的原始内容,而无需再次尝试遍历元素。

这是我的主要 class 代码

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

    }

}

这是瓷砖 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;
}

}

这是在清除 HashMap A 之前打印的内容

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

这是清除 HashMap A 并将 B 复制到其中后打印的内容。

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

没有区别。

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

当你写:

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

您没有更改任何一张地图。这些地图仅引用了 Point 个对象和 Tile 个对象。它们具有 相同的 引用 - 复制地图不会克隆其中的对象。

因此,当您更改其中一个对象的内容时,无论您使用哪个地图导航到该对象,您都会看到该更改。

换句话说,考虑这种情况:

  • 我把地址写在两张纸上
  • 我给查理一张纸,给乔一张纸
  • 查理用这张纸找到了我的房子,并将我的前门漆成了红色
  • 乔用这张纸找到我的房子并观察门的颜色

乔会看到一扇红色的前门,是吗?这里完全一样

如果您使 PointTile 类 不可变,这将不是问题 - 因为您无法更改现有对象的内容。在这一点上,复制引用和克隆对象之间没有特别有意义的区别。你最终会写成这样的东西:

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

Map<Point,Tile> B = new HashMap<Point,Tile>(A); 制作了 A 地图的浅表副本。它不会创建 PointTile 值的副本。它使用与原始地图中相同的引用。因此,当您更改 B 地图中的 Tile 时,相同的 Tile 也会在 A 地图中更改,反之亦然。

Map<Point,String> A = new HashMap<Point,String>(); 有效 因为 String 是不可变的,所以与您的 Tile 实例不同,您无法更改 String 的状态。

要创建 A 的深层副本,您必须遍历 A 的条目,创建每个键和值的副本(假设 Point 键和 Tile 值都是可变的 - 如果 Point 不是可变的,创建 Tile 值的副本就足够了),并将副本放入 B 映射中。

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

在需要深拷贝的时候进行浅拷贝。您必须手动实施深层复制或使用某些库为您完成。喜欢这个:

Java Deep-Cloning library

此处使用此实用程序class执行深度克隆。

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

效用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;
    }
}

参考:Assigning Hashmap to Hashmap

您需要通过克隆 Tile 对象来深度复制您的HashMap

默认情况下,HashMap 构造函数正在执行 浅拷贝 。它只是从传入的 Map 复制值,这适用于 primitivesStrings(和其他 immutable 对象)但不适用于对 Object 类型的引用,因为复制的引用将指向相同的原始对象,因此稍后会对其进行修改。

所以要解决这个问题,只需实现一个深度复制方法作为

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

并使您的 Tile class 实施 Cloneable 覆盖 clone() 方法作为

public class Tile implements Cloneable {

    // other implementation

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

您使用 Point 的方式,我认为也不需要 clone() 它们,但如果您也希望它们也被深度克隆,只需修改就像上面的Tile