1

我知道什么是deep copy, shallow copy,how to deep copy等等,但我的主要疑问是何时深度复制对象引用?或多久一次?


场景1:
考虑一个代码,完整代码请参见http://pastebin.com/WEgeBFNb

class Box{
        Position pos;
        Box(Position p){ 
           pos = p;      
        }
        Position getPosition(){
          return pos;
        }
   }

和一个main()喜欢:

public class Sample{
      public static void main(String args[]){
        Position pos = new Position(3,5);
        Box box = new Box(pos);     
        pos.setX(5);
        System.out.println( box.getPosition().getX()); 
            // Will print 5, but I want Box to retain its value
      }

我通过以下方式实现了上述要求:

 Box(Position p){ 
           pos = new Position(p);     // Deep cloning 
        }

然后我也必须有一个复制构造函数Position,比如:

Position(Position p){
     x  = p.x;
     y  = p.y;
   }

但我的问题是:何时使用深度克隆?

场景 2: 例如,考虑 ac# 代码。

List<Accounts> = Mysession.getAllAccounts();. 在这里,我希望返回对象的变化不能反映在会话对象中。(这种情况不仅在 C# 中,而且通常在任何oop语言中) 所以,如果我开始深度克隆,那么,这不是一件容易的事,因为它会上升到 5 级更深的对象,具有 has-a 关系

再一次,我知道要获得准确的 100%,我必须进行深度克隆。同意。

  1. 更常见的是什么?返回引用或对象的副本?
  2. 我听说,深度克隆是一个繁琐的过程,必须避免。那么多久可以进行一次深度克隆?
  3. 您能否提供一些示例场景(不需要代码)。
  4. 虽然像上面的例子一样初始化,但box 必须使用克隆pos = new Position(p)?还是正常分配pos = p
4

2 回答 2

1

与其考虑“深”或“浅”克隆,不如考虑每个封装对象引用所代表的内容。假设Foo某个类实例的字段George持有类型的引用IList<String>。这样的字段可以代表至少五种不同的事物:

  • 对不可变类型实例的引用,用于封装其中包含的字符串。

  • 对对象实例的引用,其类型可以是可变的,但永远不会暴露给任何可能改变它的对象,其保存是为了封装其中包含的字符串。

  • 唯一存在于宇宙中任何地方的唯一引用,在 George 的方法调用堆栈之外,George 用来封装其状态的可变列表。

  • 对一个列表的引用,该列表的内容可能会发生变化,它构成了某个其他对象的可变状态的一部分。该字段不用于封装列表的内容,而是用于封装其标识

  • 对列表的引用,其内容可能会改变,其内容被认为是 George 状态的一部分,并且存在外部持久引用。

如果Foo是前两种类型,则 George 的正确副本可能会Foo引用与 相同的列表George.Foo,即始终包含相同内容的新构造列表,或始终包含相同内容的任何其他列表。如果它是第三种类型,则 George 的正确副本必须Foo引用一个新列表,该列表预加载George.Foo. 如果它是第四种类型,则正确的副本必须Foo引用相同的对象George.Foo不是引用副本。如果是第五类,George不能单独克隆。

如果列表项是可变类型(而不是String),则必须确定五个用途中的哪一个适用于列表中包含的项目,并将每个列表项视为一个字段。请注意,对于逻辑上不可变的类型,其中包含的任何引用都必须是可共享的。如果一个对象的正确行为要求它持有引用的某物不是任何其他引用的目标,这意味着持有该引用的对象应该只存在一个引用。

于 2013-08-02T21:33:18.640 回答
1

面向对象编程的主要目的必须是对象保证它在任何时候都处于合法状态。

因此,当您返回对象的引用时,您应该考虑:

  1. 返回的对象是不可变的吗?
  2. 返回引用(主对象)的当前对象是否具有依赖于返回对象的值?(派生或缓存的值)

您可以通过以下方式对这些问题的答案做出反应:

返回的引用是一个不可变对象(String、BigDecimal 等)

  1. 无需采取任何行动

返回的引用是一个可变对象(数组、日期等),但主对象没有派生值(例如只装饰它)

  1. 无需采取任何行动

返回的引用是一个可变对象(数组、日期等),并且主对象具有派生值

  1. 在返回之前制作对象的副本。如果副本易于制作并且不占用内存或耗时(取决于您的非功能性要求),这适用。

  2. 返回对原始对象的不可修改的引用(就像 Collections.unmodifiable... 一样)。

  3. 返回一个代理,该代理检测对返回对象的访问并将这些更改通知主对象,以便主对象可以重新计算派生值,并且不会处于不一致状态。

当你得到一个对象引用时,问自己同样的问题。通过构造函数或方法调用。

于 2013-08-02T05:20:29.903 回答