【问题标题】:Copying a HashMap in Java在 Java 中复制 HashMap
【发布时间】:2012-04-22 04:24:24
【问题描述】:

我正在尝试保留一个包含成员的类的临时容器:

HashMap<Integer,myObject> myobjectHashMap

一个名为 myobjectsList 的类

那我做

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

然后:将一些 hashmap 项添加到 A(如 2)

然后:

myobjectListB = myobjectListA; //B has 2

然后:将 hashmap 项添加到 A(比如另外 4 个)

然后:将A返回到B中存储的项目

myobjectListA = myobjectListb;

但是当我这样做时,当我向 A 添加哈希图项时,B 会随着 A 增长。 A 现在有 6 个项目,因为 B 有 6 个。

我希望 A 在最后一次分配后仍然保留原来的 2。 在 C++ 中我会使用对象的复制,java 的等价物是什么?

补充:好的,我遗漏了一些东西来解释这一点。 MyObjectsList 不包含 HashMap,它派生自类 MyBaseOjbectsList,该类具有 HashMap 成员和 MyObjectsList 扩展 MyBaseOjbectsList。这有什么不同吗?

【问题讨论】:

  • 您能否发帖SSCCE 以便更好地了解您目前所做的工作?
  • 您的对象应该实现 Cloneable 接口,否则 MyObjectB = MyObjectA 之类的赋值只会告诉 JVM 两个变量都指向内存中的同一位置。不是两个不同的对象。
  • 顺便说一句,压倒性的成语(实际上是一条法律)是将类名大写。对于我们这些在扫描示例代码时使用这些东西作为快速提示的人来说,这将使您的示例更具可读性。
  • 添加 @KevinWelker 它还有助于语法高亮显示类名
  • @Mike - 谢谢,但尝试做可克隆的,它使它们在一个成长时都成长。我不想那样。

标签: java copy hashmap clone


【解决方案1】:

Java 支持(不深)复制概念

您可以使用以下方法对其进行归档:

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

【讨论】:

    【解决方案2】:

    从 Java 10 开始可以使用

    Map.copyOf
    

    用于创建一个浅拷贝,它也是不可变的。 (这是它的Javadoc)。对于深拷贝,正如answer you 中所述,需要某种值映射器来制作值的安全拷贝。您不需要复制 keys,因为它们必须是 不可变的

    【讨论】:

      【解决方案3】:

      如果我们想在 Java 中复制一个对象,我们需要考虑两种可能性:浅复制深复制

      浅拷贝是我们只拷贝字段值的方法。因此,副本可能依赖于原始对象。在深度复制方法中,我们确保树中的所有对象都被深度复制,因此副本不依赖于任何可能会更改的早期现有对象。

      这个问题是应用深拷贝方法的完美定义。

      首先,如果您有一个简单的HashMap&lt;Integer, List&lt;T&gt;&gt; 映射,那么我们只需创建一个这样的解决方法。创建List&lt;T&gt; 的新实例。

      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】:

        不同之处在于,在 C++ 中,您的对象位于堆栈中,而在 Java 中,您的对象位于堆中。如果 A 和 B 是对象,那么在 Java 中的任何时候都可以:

        B = A
        

        A 和 B 指向同一个对象,所以你对 A 做的任何事情都会对 B 做,反之亦然。

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

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

        【讨论】:

        • 这是否等同于说 Java 完全是通过引用而 C++ 有时是引用有时是值?但我不想要 HashMap,我不想要可以用作临时存储的类的副本。
        • 我无权访问基类中的 HashMap。同样,我必须使用从基础派生的类。我无法访问其中的 HashMap。
        【解决方案5】:

        由于 OP 提到他无法访问其中存在 HashMap 的基类 - 恐怕可用的选项很少。

        在 Java 中执行对象的深层复制的一种(非常缓慢且资源密集的)方法是滥用许多类有意或无意扩展的“可序列化”接口,然后利用它来将您的类序列化为 ByteStream .反序列化后,您将拥有相关对象的深层副本。

        可在此处找到相关指南:https://www.avajava.com/tutorials/lessons/how-do-i-perform-a-deep-clone-using-serializable.html

        【讨论】:

          【解决方案6】:

          如果你想要一份 HashMap 的副本,你需要用它构造一个新的。

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

          这将创建地图的(浅)副本。

          【讨论】:

          • 或者如果已经创建,使用myObjectListB.addAll(myObjectListA)
          • 也许我不明白这一点,但我不需要地图的副本。我需要保存地图的类的副本。 ? myObjectListB 必须是从 MyojbectsList 派生的类而不是哈希图。
          • @Kevin Welker,addAll 代表HashSetputAll 代表HashMap
          • 它没有用。在我的情况下,“新”变量仍然指向同一个对象。
          • @Thomio 我提到过,这就是“浅拷贝”的意思,如果你需要一个深拷贝就必须遍历地图并复制你需要的东西。
          【解决方案7】:

          这里有一个小(巨大)的轻描淡写。如果您想复制带有嵌套结构的HashMapHashMap.putAll() 将通过引用复制,因为它不知道如何准确复制您的对象。 For example:

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

          【讨论】:

            【解决方案8】:

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

            【讨论】:

              【解决方案9】:

              你也可以使用

              clone()
              

              将所有元素从一个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

              【讨论】:

              • 您可以,但您可能不应该,因为根据 Joshua Bloch 的 Effective Java #11,Cloneable 本质上已被破坏。
              • 另外,请注意 clone() 返回一个浅拷贝。
              • 浅甚至是什么意思?如果它没有完全复制地图(键/值完好无损),为什么还要麻烦?还不如创建一张新地图,对吧?
              • @Azurespot shallow 意味着它不会复制任何嵌套对象及其嵌套对象...
              • 如果你查看源代码,clone() 所做的只是在其中创建一个新的HashMapputAll。这完全没有意义。
              【解决方案10】:

              您将一个对象分配给另一个对象,您所做的只是将 reference 复制到该对象,而不是它的内容。您需要做的是获取对象 B 并手动将对象 A 的内容复制到其中。

              如果您经常这样做,您可以考虑在类上实现clone() 方法,该方法将创建一个相同类型的新对象,并将其所有内容复制到新对象中。

              【讨论】:

              • 克隆做了同样的事情,它似乎通过了引用。
              • 此外,当一个人增加其 HashMap 计数时,副本也会这样做,不要那样做。我想要相同类型的独立容器(在本例中为类)。
              【解决方案11】:

              在 Java 中,当你编写时:

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

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

              但是,如果你写:

              objectA = new Object()
              

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

              【讨论】:

              • 这解释了问题,解决方案是什么?还是这是 Java 的限制,无法解决?
              • 这不是限制,而是它的工作方式。如果您想要 2 个不同的对象,则创建 2 个对象 - 如果您只创建一个对象,则指向该对象的所有变量都是相等的。
              • 我建议您使用所有相关代码(最好是SSCCE)创建一个新问题,并准确解释您要达到的目标。
              • 创建一个新对象并清除原始值不是目标。目标是保留原始值,稍后在收到更多新值的副本中再次使用它们。
              • 这真的取决于对象。如果它没有提供创建新复制对象的方法,那么它就很复杂,因为该对象没有被设计为被复制(可能是故意的——但您仍然可以使用反射来完成)。如果对象确实实现了该功能(例如,请参见 HashMap 的复制构造函数的答案),那么您只需使用提供的方法。
              猜你喜欢
              • 2013-03-04
              • 2020-06-21
              • 2015-04-02
              • 2017-11-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-12-17
              • 2022-12-06
              相关资源
              最近更新 更多