在Java中复制HashMap

144
我正在尝试保留一个包含成员的类的临时容器:
HashMap<Integer,myObject> myobjectHashMap

一个名为myobjectsList的类

然后我执行

myobjectsListA = new myobjectsList();
myobjectsListB = new myobjectsList();

然后:向A中添加一些哈希映射项(例如 2)

myobjectListB = myobjectListA; //B has 2

然后:向A中添加哈希映射项(例如再添加4个)

然后:返回A以存储在B中的项

myobjectListA = myobjectListb;
但是当我这样做时,B会随着我向A添加HashMap项而增长。 现在A有6个项目,因为B有6个。 我希望A在最后一次赋值后仍然保留原始的2个。 在C ++中,我会使用对象的复制,Java中有类似的方法吗? 补充说明:好吧,我忘了解释这一点。 MyObjectsList不包含HashMap,它是从一个名为MyBaseOjbectsList的类派生的,该类具有HashMap成员,并且MyObjectsList扩展了MyBaseOjbectsList。这会有影响吗?

1
你能否发布一个SSCCE,以便更好地了解你目前所做的工作? - assylias
2
你的对象应该实现Cloneable接口,否则像MyObjectB = MyObjectA这样的赋值只是告诉JVM两个变量指向内存中的同一位置,而不是两个不同的对象。 - Mike McMahon
顺便提一下,一个压倒性的惯例(实际上是一种法则)是将类名大写。这将使得那些扫描示例代码时使用这些东西作为快速提示的人更容易阅读你的示例。 - Kevin Welker
1
补充一下 @KevinWelker 的话,这还有助于语法高亮器突出显示类名。 - ratchet freak
@Mike - 谢谢,但我尝试了可克隆,当一个增长时,它们都会增长。我不想要那个。 - user691305
@user691305请分享您可克隆的代码。您将不得不自己编写其实现。通过创建新的并分配值...等等。您需要深拷贝还是浅拷贝?...等等 :) - Mike McMahon
11个回答

272

如果你想要HashMap的副本,你需要使用构造函数创建一个新的HashMap。

myobjectListB = new HashMap<Integer,myObject>(myobjectListA);

这将创建一个(浅层)映射的副本。


5
如果还没有创建对象,可以使用 myObjectListB = new ArrayList<>(myObjectListA) 来创建一个包含相同元素的新对象;如果已经创建了对象,可以使用 myObjectListB.addAll(myObjectListA) 将 myObjectListA 中的元素添加到 myObjectListB 中。 - Kevin Welker
也许我没有理解这个,但我不需要地图的副本。我需要持有地图的类的副本。因此,myObjectListB必须是从MyojbectsList派生的类,而不是哈希映射。 - user691305
我也不理解"addAll(myOjbejctListA)"的实现。 - user691305
10
addAll 是用于 HashSet 的方法。putAll 是用于 HashMap 的方法。 - Andrea Motto
5
没成功。在我的情况下,“new”变量仍然指向同一个对象。 - Thomio
8
@Thomio,我提到了,“浅拷贝”就是这个意思。如果你需要深拷贝,就必须循环遍历地图并复制所需内容。 - ratchet freak

18

您也可以使用

clone()

复制一个HashMap的所有元素到另一个HashMap的方法

将一个HashMap的所有元素复制到另一个HashMap的程序

import java.util.HashMap;

public class CloneHashMap {    
     public static void main(String a[]) {    
        HashMap hashMap = new HashMap();    
        HashMap hashMap1 = new HashMap();    
        hashMap.put(1, "One");
        hashMap.put(2, "Two");
        hashMap.put(3, "Three");
        System.out.println("Original HashMap : " + hashMap);
        hashMap1 = (HashMap) hashMap.clone();
        System.out.println("Copied HashMap : " + hashMap1);    
    }    
}

来源: http://www.tutorialdata.com/examples/java/collection-framework/hashmap/copy-all-elements-from-one-hashmap-to-another


14
你可以这样做,但你 可能 不应该这样做,因为根据 Joshua Bloch 的《Effective Java》第11条建议,Cloneable 接口本质上是有缺陷的。 - Visionary Software Solutions
14
请注意,clone() 方法返回的是浅复制。 - Vinay W
3
“shallow”是什么意思?如果它不能完全复制地图(保持键/值不变),那还有什么用呢?不如直接创建一个新的地图,对吧? - Azurespot
3
@Azurespot所说的"shallow"意味着它不会复制任何嵌套对象及其嵌套对象... - nofunatall
2
如果你查看源代码,clone() 方法只是创建一个新的 HashMap 并将所有内容 putAll 到其中。这完全没有意义。 - Ariel

14

区别在于在C++中,对象位于堆栈上,而在Java中,对象位于堆中。如果A和B是对象,在Java中任何时候执行以下操作:

B = A

A和B指向同一个对象,所以对A所做的任何操作都会对B产生影响,反之亦然。

如果您想要两个不同的对象,请使用新的HashMap()

您可以使用Map.putAll(...)在两个Map之间复制数据。


这是否意味着Java全部都是按引用传递,而C++有时是按引用传递,有时是按值传递?但我不想要一个HashMap,我想要一个类的副本,可以用作临时存储。 - user691305
我无法访问基类中的HashMap。同样,我必须使用派生自基类的类进行工作。我无法访问其中的HashMap。 - user691305

9

自Java 10起,可以使用

Map.copyOf

为创建一个浅拷贝,它也是不可变的(这里是它的Javadoc)。对于深度拷贝,正如在这个答案中提到的,您需要一些值映射器来安全地复制值。但是您不需要复制,因为它们必须是不可变的


6

这里有一个小(非常大)的低估。如果你想要复制一个带有嵌套结构的HashMapHashMap.putAll() 会按引用进行复制,因为它不知道如何准确地复制你的对象。例如:

import java.util.*;
class Playground {
    public static void main(String[ ] args) {
        Map<Integer, Map<Integer,List<Float>>> dataA = new HashMap<>();
        Map<Integer, Map<Integer,List<Float>>> dataB = new HashMap<>();

        dataA.put(1, new HashMap<>());
        dataB.putAll(dataA);

        assert(dataB.get(1).size() == 0);

        dataA.get(1).put(2, new ArrayList<>());

        if (dataB.get(1).size() == 1) { // true
            System.out.println(
                "Sorry object reference was copied - not the values");
        }
    }
}

所以基本上您需要自己复制字段,就像这里一样。
List <Float> aX = new ArrayList<>(accelerometerReadingsX);
List <Float> aY = new ArrayList<>(accelerometerReadingsY);

List <Float> gX = new ArrayList<>(gyroscopeReadingsX);
List <Float> gY = new ArrayList<>(gyroscopeReadingsY);

Map<Integer, Map<Integer, Float>> readings = new HashMap<>();

Map<Integer,List<Float>> accelerometerReadings = new HashMap<>();
accelerometerReadings.put(X_axis, aX);
accelerometerReadings.put(Y_axis, aY);
readings.put(Sensor.TYPE_ACCELEROMETER, accelerometerReadings);

Map<Integer,List<Float>> gyroscopeReadings = new HashMap<>();
gyroscopeReadings.put(X_axis, gX);
gyroscopeReadings.put(Y_axis, gY);
readings.put(Sensor.TYPE_GYROSCOPE, gyroscopeReadings);

6
如果我们想在Java中复制一个对象,我们需要考虑两种可能性:浅拷贝和深拷贝。
浅拷贝只复制字段值,因此副本可能依赖于原始对象。而深拷贝则确保树中的所有对象都被深度复制,因此副本不依赖于任何之前存在的对象,这些对象可能会发生变化。
这个问题是应用深拷贝方法的完美定义。
首先,如果你有一个简单的HashMap > map,则可以像这样创建一个解决方法。创建一个新的List实例。
public static <T> HashMap<Integer, List<T>> deepCopyWorkAround(HashMap<Integer, List<T>> original)
{
    HashMap<Integer, List<T>> copy = new HashMap<>();
    for (Map.Entry<Integer, List<T>> entry : original.entrySet()) {
        copy.put(entry.getKey(), new ArrayList<>(entry.getValue()));
    }
    return copy;
}

这个方法使用Stream.collect()来创建克隆映射,但是与前一种方法的思路相同。
public static <T> Map<Integer, List<T>> deepCopyStreamWorkAround(Map<Integer, List<T>> original)
{
    return original
            .entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, valueMapper -> new ArrayList<>(valueMapper.getValue())));
}   

但是,如果 T 内部的实例也是可变对象,我们就会有一个大问题。在这种情况下,真正的深度复制是一种解决该问题的选择。它的优点在于,对象图中每个可变对象都会被递归地复制。由于复制不依赖于先前创建的任何可变对象,因此不会像浅复制那样意外地修改。
为了解决这个问题,可以使用深度复制实现。
public class DeepClone
{
    public static void main(String[] args)
    {
        Map<Long, Item> itemMap = Stream.of(
                entry(0L, new Item(2558584)),
                entry(1L, new Item(254243232)),
                entry(2L, new Item(986786)),
                entry(3L, new Item(672542)),
                entry(4L, new Item(4846)),
                entry(5L, new Item(76867467)),
                entry(6L, new Item(986786)),
                entry(7L, new Item(7969768)),
                entry(8L, new Item(68868486)),
                entry(9L, new Item(923)),
                entry(10L, new Item(986786)),
                entry(11L, new Item(549768)),
                entry(12L, new Item(796168)),
                entry(13L, new Item(868421)),
                entry(14L, new Item(923)),
                entry(15L, new Item(986786)),
                entry(16L, new Item(549768)),
                entry(17L, new Item(4846)),
                entry(18L, new Item(4846)),
                entry(19L, new Item(76867467)),
                entry(20L, new Item(986786)),
                entry(21L, new Item(7969768)),
                entry(22L, new Item(923)),
                entry(23L, new Item(4846)),
                entry(24L, new Item(986786)),
                entry(25L, new Item(549768))
        ).collect(entriesToMap());


        Map<Long, Item> clone = DeepClone.deepClone(itemMap);
        clone.remove(1L);
        clone.remove(2L);

        System.out.println(itemMap);
        System.out.println(clone);
    }

    private DeepClone() {}

    public static <T> T deepClone(final T input)
    {
        if (input == null) return null;

        if (input instanceof Map<?, ?>) {
            return (T) deepCloneMap((Map<?, ?>) input);
        } else if (input instanceof Collection<?>) {
            return (T) deepCloneCollection((Collection<?>) input);
        } else if (input instanceof Object[]) {
            return (T) deepCloneObjectArray((Object[]) input);
        } else if (input.getClass().isArray()) {
            return (T) clonePrimitiveArray((Object) input);
        }

        return input;
    }

    private static Object clonePrimitiveArray(final Object input)
    {
        final int length = Array.getLength(input);
        final Object output = Array.newInstance(input.getClass().getComponentType(), length);
        System.arraycopy(input, 0, output, 0, length);
        return output;
    }

    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;
        if (input instanceof LinkedList<?>) {
            clone = new LinkedList<>();
        } else if (input instanceof SortedSet<?>) {
            clone = new TreeSet<>();
        } else if (input instanceof Set) {
            clone = new HashSet<>();
        } else {
            clone = new ArrayList<>();
        }

        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;
        if (map instanceof LinkedHashMap<?, ?>) {
            clone = new LinkedHashMap<>();
        } else if (map instanceof TreeMap<?, ?>) {
            clone = new TreeMap<>();
        } else {
            clone = new HashMap<>();
        }

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

        return clone;
    }
}

4
在Java中,当你写下如下代码时:

Object objectA = new Object();
Object objectB = objectA;

objectAobjectB是相同的,并指向同一引用。更改其中一个将会改变另一个。因此,如果您更改objectA的状态(而不是它的引用),objectB也将反映出该更改。

然而,如果您这样写:

objectA = new Object()

那么,objectB 仍然指向您创建的第一个对象(原始的 objectA),而 objectA 现在指向一个新的对象。


这个问题是什么,有什么解决办法吗?还是这是Java的一个无法绕过的限制? - user691305
1
这不是一个限制,而是它的工作方式。如果你想要两个不同的对象,你就创建两个对象——如果你只创建一个对象,那么指向该对象的所有变量都是相等的。 - assylias
我建议您创建一个新的问题,包含所有相关代码(最好是SSCCE),并准确解释您想要实现什么。 - assylias
创建一个新对象并清除原始值不是目标。目标是保留原始值,并仅在接收更多新值的副本中再次使用它们。 - user691305
这真的取决于对象本身。如果它没有提供创建新副本对象的方法,那么就很复杂,因为该对象并未被设计为可复制(也许是有意为之 - 但您仍然可以使用反射来实现)。如果该对象确实实现了该功能(例如使用HashMap的复制构造函数的答案),那么您只需使用提供的方法即可。 - assylias
复制构造函数或复制静态工厂通常是这里的最佳选择。 - Visionary Software Solutions

1
由于这个问题仍未得到解答,而我遇到了类似的问题,所以我会尝试回答一下。问题(正如其他人已经提到的)是你只是复制了对同一对象的引用,因此在副本上进行修改也会修改原始对象。因此,你需要复制对象(你的映射值)本身。最简单的方法是让所有对象都实现serializeable接口。然后将你的映射序列化和反序列化以获得真正的副本。你可以自己做,也可以使用apache commons SerializationUtils#clone(),你可以在这里找到它:https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/SerializationUtils.html 但请注意,这是最简单的方法,但序列化和反序列化大量对象是一个昂贵的任务。

1

Java复制HashMap

Java支持浅拷贝(不是深拷贝)的概念

您可以使用以下方法进行实现:

  • 构造函数
  • clone()
  • putAll()

0
当你将一个对象赋值给另一个对象时,实际上只是复制了对象的引用,而不是它的内容。你需要做的是将对象A的内容手动复制到对象B中。
如果你经常这样做,可以考虑在类上实现一个`clone()`方法,该方法将创建一个相同类型的新对象,并将所有内容复制到新对象中。

克隆做了同样的事情,只是似乎传递了引用。 - user691305
此外,当一个HashMap计数增加时,副本也会增加,这是不希望的。我想要相同类型的独立容器(在这种情况下是类)。 - user691305

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