【问题标题】:How to clone a Java object with the clone() method如何使用 clone() 方法克隆 Java 对象
【发布时间】:2011-11-26 15:39:24
【问题描述】:

我不明白克隆自定义对象的机制。例如:

public class Main{

    public static void main(String [] args) {

        Person person = new Person();
        person.setFname("Bill");
        person.setLname("Hook");

        Person cloned = (Person)person.clone();
        System.out.println(cloned.getFname() + " " + cloned.getLname());
    }
}

class Person implements Cloneable{

    private String fname;
    private String lname;

    public Object clone() {

        Person person = new Person();
        person.setFname(this.fname);
        person.setLname(this.lname);
        return person;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public void setLname(String lname){
        this.lname = lname;
    }

    public String getFname(){
        return fname;
    }

    public String getLname() {
        return lname;
    }
}

这是一个示例,显示了书中所写的正确克隆方法。但是我可以在类名定义中删除实现 Cloneable 并且得到相同的结果。

所以我不明白 Cloneable 的提议以及为什么在 Object 类中定义 clone() 方法?

【问题讨论】:

标签: java clone


【解决方案1】:

clone 方法用于进行深层复制。确保您了解深拷贝和浅拷贝之间的区别。在您的情况下,复制构造函数可能是您想要的模式。但是,在某些情况下,您不能使用此模式,例如,因为您是 X 类的子类,并且您无权访问您需要的 X 的构造函数。如果 X 正确地覆盖了它的 clone 方法(如果需要),那么你可以通过以下方式进行复制:

class Y extends X implements Cloneable {

    private SomeType field;    // a field that needs copying in order to get a deep copy of a Y object

    ...

    @Override
    public Y clone() {
        final Y clone;
        try {
            clone = (Y) super.clone();
        }
        catch (CloneNotSupportedException ex) {
            throw new RuntimeException("superclass messed up", ex);
        }
        clone.field = this.field.clone();
        return clone;
    }

}

通常在覆盖您的克隆方法时:

  • 使返回类型更具体
  • 首先调用super.clone()
  • 当您知道 clone() 也适用于任何子类时,不要包含 throws 子句(克隆模式的弱点;如果可能,将类设为 final)
  • 单独保留不可变和原始字段,但在调用 super.clone() 后手动克隆可变对象字段(克隆模式的另一个弱点,因为这些字段不能成为最终字段)

Objectclone() 方法(最终将在所有超类都遵守约定时调用)进行浅拷贝并处理新对象的正确运行时类型。注意整个过程中没有调用构造函数。

如果您希望能够在实例上调用clone(),则实现Cloneable 接口并公开该方法。如果您不想在实例上调用它,但您确实想确保子类可以调用它们的 super.clone() 并获得它们需要的东西,那么不要实现 Cloneable 并保留方法 protected如果您的超类尚未将其声明为公开。

克隆模式很困难并且有很多陷阱。确保它是您需要的。考虑复制构造函数或静态工厂方法。

【讨论】:

    【解决方案2】:

    克隆并不像 Joshua bloch 所说的那样成熟:

    ​​>

    http://www.artima.com/intv/bloch13.html

    【讨论】:

      【解决方案3】:

      这里不需要在clone() 方法中显式创建对象。只需调用super.clone() 即可创建此对象的副本。它将执行浅克隆。

      【讨论】:

        【解决方案4】:

        在您的示例中,您没有进行实际克隆。您被覆盖了对象类的 clone() 方法并给出了您自己的实现。但是在您的克隆方法中,您正在创建一个新的 Person 对象。并返回它。所以在这种情况下,实际对象没有被克隆。

        所以你的克隆方法应该是这样的:

        public Object clone() {
              return super.clone();
          }
        

        所以这里的克隆将由超类方法处理。

        【讨论】:

          【解决方案5】:

          Object 类中的clone() 对内存进行浅拷贝,而不是调用构造函数等方法。为了在任何本身没有实现clone() 的对象上调用clone(),您需要实现Clonable 接口。

          如果您覆盖 clone() 方法,则不必实现该接口。

          正如 JavaDoc 所说,这将导致异常:

          class A {
            private StringBuilder sb; //just some arbitrary member
          }
          
          ...
          
          new A().clone(); //this will result in an exception, since A does neither implement Clonable nor override clone()
          

          如果示例中的A 将实现Clonable,则调用clone()Object 版本)将生成一个新的A 实例,该实例引用非常相同 StringBuilder,即对克隆实例中的sb 的更改将导致对原始A 实例中的sb 的更改。

          这意味着浅拷贝,这也是为什么通常最好覆盖 clone() 的原因之一。

          编辑:作为旁注,使用返回类型协方差将使您覆盖的 clone() 更加明确:

          public Person clone() {
            ...
          }
          

          【讨论】:

          • Thomas,如果我没有在 A 中实现 Cloneable 并且没有覆盖 clone(),我会收到编译错误,因为 clone() 是受保护的
          • @Dmytro 是的,这个例子只是为了说明这一点。您可以调用clone(),即使它使用反射进行保护,或者您可以在覆盖的版本中调用super.clone() - 在这两种情况下,当Clonable 接口丢失时,您都会遇到异常。
          【解决方案6】:

          如果你不声明可克隆接口,当你调用克隆方法时你应该得到 CloneNotSupportException。如果你声明然后调用克隆方法,它将进行浅拷贝。

          【讨论】:

            【解决方案7】:

            JVM 能够为您克隆对象,因此您不应该自己构造一个新人。只需使用此代码:

            class Person implements Cloneable {
                // ...
                @Override
                public Object clone() throws CloneNotSupportedException {
                    return super.clone();
                }
            }
            

            class Person implements Cloneable {
                // ...
                @Override
                public Object clone() {
                    try {
                        return super.clone();
                    }
                    catch (CloneNotSupportedException e) {
                        throw new Error("Something impossible just happened");
                    }
                }
            }
            

            即使 Person 类是子类,这也可以工作,而您的克隆实现将始终创建 Person 的实例(而不是 Employee 的 Employee 实例,例如)。

            【讨论】:

              【解决方案8】:

              它与任何此类界面的目的相同。主要是它允许方法(等)接受任何可克隆对象并可以访问他们需要的方法,而不会将自己限制为一个特定的对象。诚然,Clonable 可能是在这方面不太有用的接口之一,但肯定有你可能需要它的地方。如果您想了解更多信息,请考虑使用 Comparable 接口,例如它允许您对列表进行排序(因为列表不需要知道对象是什么,只需知道它们可以进行比较)。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2020-03-07
                • 2013-03-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-08-20
                • 2017-08-10
                相关资源
                最近更新 更多