【问题标题】:Cloning in JavaJava中的克隆
【发布时间】:2011-11-14 18:48:27
【问题描述】:

我从网上读到一段关于克隆的文章。但是我不太明白,谁能解释清楚?

如果类有 final 字段,则不能在 clone 方法中为这些字段赋值。这会导致无法正确初始化对象的最终字段。如果 final 字段是指对象的某些内部状态,那么克隆的对象最终会共享内部状态,这对于可变对象肯定是不正确的。

供参考,以下是链接: http://www.jusfortechies.com/java/core-java/cloning.php

【问题讨论】:

标签: java


【解决方案1】:

总之

  • o.clone() 调用Object.clone(),这会生成o 的内存副本。此后复制的引用无法更改为 final 字段,因此我们有非自愿的别名;
  • 不想要的混叠和可变性不协调:如果o 发生变化,克隆也会不自觉地发生变化;
  • the blog post you cite 中的语句是从this much better blog post 复制而来的,它用良好的代码示例证明了这些语句。

关于通过 `Object.clone()' 进行克隆的详细信息和示例

我相信这篇文章是在谈论通过克隆链接(通过super.clone())的clone(),这样最终会调用Object.clone(),这会生成通过本机代码克隆的对象的平面内存副本。

假设我们有以下示例(来自the blog post mentioned below):

public class Person implements Cloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
        // ...
}

person2 = (Person) person1.clone();

那么 person2 与 person1 具有相同的字段大脑记忆部分,即两者都对同一个大脑拥有相同的引用。那么由于 Person 对象是可变的,它们可以学习一些东西:

person1.learn(dvorakTyping);

然后神奇地 person2 也可以在 dvorak 键盘上打字。对于不可变对象,这个问题不会发生(尽管克隆仍然存在问题,因为 final 字段仍然不能通过参数初始化,就像在构造函数中一样)。

通过构造函数调用克隆

我上半句的原因:您可以通过调用对象的构造函数之一来实现克隆。有些人声称这违反了克隆人的合同,但事实并非如此。这是 good blog post 关于为什么在克隆中调用构造函数的原因(一个主要原因是那些最终字段)。


更新

阅读 Hemal 对 mre 答案的评论,我确实浏览了引用问题的博客文章,结果发现,该文章复制了 the blog post I cited 的一些句子,但没有非常好的代码示例。哈哈。

【讨论】:

  • 链接没有进入最后的帖子。请更新。所以基本上你说的是 memcpy 对克隆来说是一个糟糕的实现?嗯...是的。
  • 是的,memcpy 解决方案肯定有其不足之处。但它有一个(对我来说不太重要的)优点,即子类不需要重写 clone() 方法来调用它们自己的构造函数,因为 memcpy 只是获取对象 - 与它来自哪个子类无关 - 并创建一个 memcpy。
  • 完全同意——能够使用 memcpy 是一个很小的好处。这不是非常类似于需要用户定义的复制构造函数的 C++ 规则吗?
  • 我不太适合 C++,但我认为他们的复制构造函数更接近于在克隆中调用构造函数的广告解决方案。在 Java 中,我是复制和转换构造函数的忠实粉丝,通常通过 clone() 或其他方式实现它们。
【解决方案2】:

我也不明白,除非一个类不能实现一个行为良好的clone 方法,如果它的所有字段都没有一个行为良好的clone 方法。

【讨论】:

    【解决方案3】:

    我不推荐它,但可以使用 sun.misc.Unsafe 覆盖最终字段的值。 强烈不建议使用这个类,但这篇文章不是关于那个()。 覆盖最终字段值的示例代码:

        public class FinalClone
        implements Cloneable {
    private final FinalClone finalField;
    
    public FinalClone(FinalClone finalField) {
        this.finalField = finalField;
    }
    
    @Override
    protected FinalClone clone()
            throws CloneNotSupportedException {
    
        final FinalClone result = (FinalClone) super.clone();
    
        if (finalField == null) {
            return result; // no need to clone null
        }
    
        final Field unsafeField;
        try {
            unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        }
        catch (NoSuchFieldException e) {
            throw new AssertionError(e);
        }
        unsafeField.setAccessible(true);
        final Unsafe unsafe;
        try {
            unsafe = (Unsafe) unsafeField.get(null);
        }
        catch (IllegalAccessException e) {
            throw new SecurityException(e);
        }
    
        // Update final field
        try {
            unsafe.putObjectVolatile(
                    result,
                    unsafe.objectFieldOffset(
                            FinalClone.class.getDeclaredField("finalField")),
                    finalField.clone());
        }
        catch (NoSuchFieldException e) {
            throw new AssertionError(e);
        }
    
        return result;
    }
    }
    

    【讨论】:

    • 这种操作在标准的Reflection API中有官方的支持,所以不需要使用特定实现的这个非标准的内部类。干净的解决方案会更简单……
    【解决方案4】:

    克隆 final 字段没有任何问题,它与其他字段一样有效,但并非一直有效。

    有时它会成为可变对象的问题

    当我们默认使用克隆时,它会提供浅拷贝(对同一个对象的引用),因此在覆盖克隆时,我们会尝试对所有可变对象进行深度复制。当我们尝试深度复制最终字段时,会出现最终引用(最终字段的最终引用) ) 不能重新分配给新对象。

    public class Person implements Cloneable
    {
        private final Brain brain; 
        private int age;
        public Person(Brain aBrain, int theAge)
        {
            brain = aBrain; 
            age = theAge;
        }
        public Object clone()
        {
            try
            {
                Person another = (Person) super.clone();
                // shallow copy made so far. Now we will make it deep
    
                another.brain = (Brain) brain.clone();
    
    //ERROR: you can't set another.brain 
    
                return another;
            }
            catch(CloneNotSupportedException e) {} 
            //This exception will not occur
        }
    
    }
    

    此示例来自 Davefar 在另一个答案中提到的同一 blog

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-17
      • 2011-02-12
      • 2018-08-20
      相关资源
      最近更新 更多