克隆是不是比构造函数/工厂方法提供了性能改进?

Posted

技术标签:

【中文标题】克隆是不是比构造函数/工厂方法提供了性能改进?【英文标题】:Does cloning provide a performance improvement over constructors/factory methods?克隆是否比构造函数/工厂方法提供了性能改进? 【发布时间】:2010-10-14 08:48:31 【问题描述】:

我正在维护一个较旧的 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
  

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

【问题讨论】:

看起来很奇怪。我会使用构造函数来实例化新对象。 【参考方案1】:

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

【讨论】:

将其提高 +1 是因为(在查看代码之后)它看起来比我更深奥的答案更适用或更可能适用。 请注意,这是克隆的主要目的(假设我的教授知道他们在说什么)。 +1:是的,这里.clone() 似乎是Javaish 的值传递方式。 是的,但问题是为什么代码作者选择使用 clone() 而不是通过复制构造函数或工厂方法等替代方法来创建副本。 @Derek Mahar:正确设计的克隆方法将返回与原始对象相同类型的对象。复制构造函数和工厂方法都不能做这些事情,除非一个类型支持一个可覆盖的函数来返回一个工厂,该工厂将返回它自己类型的对象。【参考方案2】:

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

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

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

Cloneable 接口的poor design 上查看此code example 和以前的discussion。

【讨论】:

【参考方案3】:

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

可以通过使复制构造函数“受保护”并在每个派生类中拥有一个可覆盖的工厂复制方法来解决这个问题,该方法调用该类自己的复制构造函数,而后者又调用其基类的复制构造函数.但是,无论是否添加任何新字段,每个派生类都需要复制构造函数和复制方法的覆盖。如果case类使用了“clone”,这个多余的代码就可以去掉了。

【讨论】:

【参考方案4】:

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

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

【讨论】:

怎么样?我虽然原型语义只是意味着您可以在运行时更改构造函数或其他字段或方法的行为。 克隆允许您设置初始值等,并且(通过使用委托)也可以更改行为。这有点像一个杂物,但你通常不会完全成熟的自我风格语义,只是一点点果汁,所以它通常在实践中有效。【参考方案5】:

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

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

编辑:添加代码以表明 clone 不必做与构造函数相同的工作:

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);
    

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

【讨论】:

如果您要标记某些内容,请至少评论您认为不准确的地方! 如果复制构造函数做了昂贵的工作,那么克隆也必须做同样的事情。 克隆绕过了构造函数——你是怎么得到它的? @Tom,是的,您可以使用复制构造函数而不是调用 super.clone... 但如果它完成了昂贵的工作而不是简单的复制并且不需要该工作,那么它会编码不好。

以上是关于克隆是不是比构造函数/工厂方法提供了性能改进?的主要内容,如果未能解决你的问题,请参考以下文章

为什么Java Pattern类使用工厂方法而不是构造函数?

不可变类/对象、私有构造函数、工厂方法

js - 创建对象的几种方式(工厂模式构造函数模式原型模式)

第一条:用静态工厂方法代替构造器

js对象工厂函数与构造函数

Effective Java大厂实战之考虑以静态工厂方法代替构造方法