7

我正在维护一个较旧的 Java 代码库(jvm 1.4),它似乎使用克隆作为对象实例化的替代方法,我猜它是一种性能优化。这是一个人为的例子:

public class Foo {
  private SomeObject obj; // SomeObject implements Cloneable
  public Foo() {
    obj = new SomeObject();
    obj.setField1("abc"); // these fields will have the same value every time
    obj.setField2("def");
  }
  public void doStuff() {
    SomeObject newObj = obj.clone(); // clone it instead of using a factory method
    // do stuff with newObj
  }
}

尽管有关于过早优化的常见警告,但这实际上是在某些时候推荐的习语吗?

4

5 回答 5

4

大概他们想要一份副本。也许他们想将它传递给另一个函数,并且不能确定该函数不会改变它。这是一种确保方法 doStuff() 相对于它所调用的 Foo 对象的状态为 const 的方法。

于 2009-03-19T17:10:09.307 回答
3

调用clone()而不是复制构造函数或工厂方法的一个原因是可能没有其他选项可用。

与实现clone()复制构造函数或工厂方法来执行相同操作相比,实现执行浅对象复制(涉及深度复制更多)是微不足道的。要实现clone(),一个类需要简单地实现Cloneable接口并clone()用一个调用super.clone()通常调用的方法覆盖方法Object.clone()Object.clone()将原始对象的每个属性复制到副本的相应属性中,从而创建浅拷贝。

虽然实现clone()很简单,但仍然很容易忘记实现Cloneable。因此,使用clone()复制对象的潜在风险是,如果该对象的类确实忽略了直接或间接实现Cloneableclone()调用Object.clone(),它将抛出CloneNotSupportedException.

请参阅此代码示例和先前关于界面设计不佳讨论。Cloneable

于 2009-11-11T19:03:20.520 回答
2

复制构造函数的一个主要问题是必须在编译时知道对象的类型。如果可继承类支持复制构造函数,并且构造函数传递了派生类对象,则构造函数将生成一个基类对象,其基类属性通常与传入对象的属性匹配,但新对象不会t 支持传入对象中存在但基类中不存在的任何特性。

可以通过使复制构造函数“受保护”并在每个派生类中拥有一个可覆盖的工厂复制方法来解决这个问题,该方法调用该类自己的复制构造函数,而后者又调用其基类的复制构造函数。但是,无论是否添加任何新字段,每个派生类都需要一个复制构造函数和复制方法的覆盖。如果案例类使用“克隆”,则可以消除此额外代码。

于 2011-03-09T18:21:32.390 回答
1

这可能是一种性能优化,具体取决于构造函数中完成了多少工作。

它更有可能被使用,因为语义不同。克隆提供了一种以通常不倾向于这种方式的语言实现“原型语义”(如在 javascript、self 等中)的方法。

于 2009-03-19T17:10:46.013 回答
0

如果 SomeObject 构造函数执行昂贵的工作,例如从数据库中获取某些内容或解析某些内容,或者从文件中读取某些内容,那么克隆将有意义以避免执行该工作。

如果构造函数什么都不做,那么真的没有必要使用克隆。

编辑:添加代码以显示克隆不必与构造函数执行相同的工作:

class Main
    implements Cloneable
{
    private final double pi;

    public Main()
    {
        System.out.println("in Main");
        // compute pi to 1,000,000,000 decimal palaces
        pi = 3.14f;
    }

    public Object clone()
    {
        try
        {
            return (super.clone());
        }
        catch(final CloneNotSupportedException ex)
        {
            throw new Error(); // would not throw this in real code
        }
    }


    public String toString()
    {
        return (Double.toString(pi));
    }

    public static void main(String[] args)
    {
        final Main a;
        final Main b;

        a = new Main();
        b = (Main)a.clone();

        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
}

Main 构造器被调用一次,计算 pi 被执行一次。

于 2009-03-19T17:27:58.763 回答