【问题标题】:Java shallow copy, not working as expected [duplicate]Java浅拷贝,没有按预期工作[重复]
【发布时间】:2020-06-15 05:41:50
【问题描述】:

这对你们中的许多人来说可能非常微不足道,但我找不到答案。提前感谢您的帮助。

简而言之,我浅拷贝了一个列表,然后修改其中一个元素,但更改不会反映在另一个列表中,尽管几乎每个资源都说它们只是对同一个对象的两个引用,并且在一个应该反映在另一个中。

附:我已经检查了诸如this one 之类的问题,这些问题阐明了黑白浅拷贝和深拷贝的区别。我的问题是,为什么尽管有这些理论,但有时浅拷贝的行为就像深拷贝一样。

    List<Integer> lst1 = new ArrayList<>();
    lst1.add(2);
    lst1.add(6);
    lst1.add(1);
    lst1.add(4);
    lst1.add(9);
    lst1.add(5);

    List<Integer> lst2 = new ArrayList<>(lst1);
    lst1.set(0, 3);

    System.out.println("lst1 = " + lst1);
    System.out.println("lst2 = " + lst2);

结果:

lst1 = [3, 6, 1, 4, 9, 5]

lst2 = [2, 6, 1, 4, 9, 5]

【问题讨论】:

  • lst2 是一个新列表,将 lst1 传递给 new ArrayList.. 会创建它的副本

标签: java list collections copy


【解决方案1】:

你所拥有的是一个浅拷贝,是的,但是任何拷贝都会显示你描述的变化。如果它根本不是副本,它会显示更改,例如List&lt;Integer&gt; lst2 = lst1;.

浅拷贝和深拷贝之间的区别在于数组中的可变元素会发生什么。这是一个显示浅拷贝和深拷贝之间区别的示例。

List<List<Integer>> lst1 = new ArrayList<>();
lst1.add(new ArrayList<>(Arrays.asList(5)));
System.out.println(lst1); // [[5]]
List<List<Integer>> lstShallowCopy = new ArrayList<>(lst1); // shallow copy
List<List<Integer>> lstDeepCopy = lst1.stream()
   .map(ArrayList::new)
   .collect(toList()); // deep copy
List<List<Integer>> lstNotACopy = lst1; // not a copy at all

lst1.get(0).add(6);
lst1.add(Arrays.asList(7));

System.out.println(lst1); // [[5, 6], [7]]
System.out.println(lstShallowCopy); 
  // [[5, 6]]: shows modifications to the original elements, but 
  // doesn't include new elements
System.out.println(lstDeepCopy); // [[5]]
  // shows no modifications at all
System.out.println(lstNotACopy); // [[5, 6], [7]]
  // shows all modifications

【讨论】:

    【解决方案2】:

    对于深拷贝和浅拷贝的含义似乎存在混淆。

    浅拷贝是包含相同对象的新列表。你可以证明这是这样发生的:

    List<AtomicBoolean> a = Arrays.asList(new AtomicBoolean(true));
    List<AtomicBoolean> b = new ArrayList<>(a);
    b.get(0).set(false);
    a == b // false
    a.get(0) == a.get(b) // true
    

    ab 是不同的列表,但它们包含所有相同的对象。

    深拷贝需要复制列表中的所有对象。在这种情况下,它将是:

    List<AtomicBoolean> b = a.stream().map(value -> new AtomicBoolean(value.get())).collect(toList());
    

    但是,请注意,由于int 是不可变的,因此浅拷贝和深拷贝之间没有区别。

    还要注意,浅拷贝仍然是不同的对象,并且更改其属性不会反映在原始对象上。在您的情况下,调用 .set 只会更改副本。

    【讨论】:

    • “但是,请注意,由于 int 是不可变的”,这是导致此代码无法按预期工作的关键部分。如果您想在intInteger 中显示此行为,您可以将原语包装在一个类中。例如class IntWrapper{ public int value;}
    • @ChristopherSchneider Integer 是否不可变并不重要。 OP 在一个列表上调用set,它替换了该列表中的元素,无论是否不可变。这永远不会对先前创建的副本产生影响。构造一个修改对象而不是列表的反例是不可能的。
    • @Holger 是的,你是对的。我没有仔细查看初始代码。促使我做出回应的是我认为他们所期望的,那就是,“由于这是一个 Integer 对象,我只是在更新对整数的引用,这应该反映在两个列表中。”但由于它们实际上只是在索引 0 处设置了一个全新的值,所以这无关紧要。
    【解决方案3】:

    new 关键字导致创建一个全新的列表,因为您将 lst2 分配给 new 列表。 任何时候使用new 关键字,都会创建一个以前没有被引用过的全新对象。由于 lst2 被实例化为 new 列表,因此此 new 列表在内存中具有与前一个列表分开的自己的空间。

    另外,如果您的列表包含对可变对象的引用,而不是整数,那就另当别论了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-24
      • 2011-02-09
      • 1970-01-01
      • 1970-01-01
      • 2017-10-14
      • 2016-05-07
      • 2012-04-12
      • 2015-01-13
      相关资源
      最近更新 更多