克隆如何比对象创建具有更高的性能

Posted

技术标签:

【中文标题】克隆如何比对象创建具有更高的性能【英文标题】:How clone has more performance than object creation 【发布时间】:2015-04-27 20:10:42 【问题描述】:

我试图了解 java 中的 clone() 方法下面发生了什么,我想知道如何比进行新调用更好

public class Person implements Cloneable 

    private String firstName;
    private int id;
    private String lastName;

    //constructors, getters and setters

    @Override
    protected Object clone() throws CloneNotSupportedException 
        Person p = (Person) super.clone();
        return p;
    


这是我的克隆代码,我想知道下面发生了什么,以及新调用之间的区别是什么。

这是我的客户代码

    Person p = new Person("John", 1, "Doe");
    Person p2 = null;
    try 
         p2 = (Person) p.clone();
     catch (CloneNotSupportedException ex) 
        Logger.getLogger(clientPrototype.class.getName()).log(Level.SEVERE, null, ex);
    
    p2.setFirstName("Jesus");
    System.out.println(p);
    System.out.println(p2);

【问题讨论】:

为什么你认为 clone() 比创建对象的性能更好?这不是真的。 首先使用clone()也是not a good的想法。 你会用什么来代替克隆,还有,为什么真正的克隆没有更好的性能? .clone() 的一个与性能无关的优势(假设它在下面使用Object.clone())是它生成一个与调用它的对象具有相同运行时类的对象,而如果你使用new,您将在编译时硬编码您正在创建的对象的类,这可能与对象的确切运行时类不同(对象的类可以是编译时类型的子类指向它的变量)。 一如既往地谈论clone,尽量不惜一切代价避免。这是一个损坏的功能,非常容易引入错误并且极难实现正确。并且更难通过适当的继承来维护正确的实现(Effective Java 对此主题有很好的阅读)。有更安全和更好的方法来创建副本。例如,通过向您的类引入 复制构造函数 【参考方案1】:

我已经为 Person 类创建了 simple benchmark:

public class Person 

    private String name;
    private int age;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public int getAge() 
        return age;
    

    public void setAge(int age) 
        this.age = age;
    

并得到以下结果:

Benchmark             Mode  Cnt     Score       Error   Units

MyBenchmark.viaClone  avgt   10     10.041 ±    0.059   ns/op
MyBenchmark.viaNew    avgt   10      7.617 ±    0.113   ns/op

这个简单的基准测试表明,实例化新对象并从源对象设置相应的属性比克隆它所花费的时间少 25%。

【讨论】:

我向您的 repo 提交了一个 PR,在基准测试中添加了一个 Clone Constructor 方法。在带有 jdk11 的 macbook pro 上进行基准测试时,所有 3 种方法的“分数”约为 4 ns/op,性能差异可以忽略不计。 github.com/aaabramov/benchmark-it/pull/12【参考方案2】:

我的要求是为一个类创建 1000 个对象。所有这些对象都具有大多数共同的属性。因此,我决定创建一个具有通用属性的基础对象并将其克隆并在克隆对象上设置对象特定属性。对此会有什么性能影响?我用不同的方法尝试了与上面相同的示例,但我发现没有太大的稳定性能差异。这是我的代码和结果。

import java.util.*;
import java.util.stream.*;
import java.text.*;
public class Test
    public static void main(String[] args)

        try
            SimpleDateFormat sdf  = new SimpleDateFormat("yyyy-MM-dd");
            long start = System.currentTimeMillis();
            SDFHolder holder = new SDFHolder();
            holder.setSdf(sdf);
            for(int i = 0; i < 1000000; i++)
                SDFHolder locHolder = (SDFHolder)holder.clone();
            
            System.out.println("Cloning : " + (System.currentTimeMillis() - start) + " ms");
            start = System.currentTimeMillis();
            for(int i = 0; i < 100000000; i++)
                SDFHolder locHolder = new SDFHolder();
                locHolder.setSdf(sdf);
            
            System.out.println("Creating : " + (System.currentTimeMillis() - start) + " ms");
         catch(Exception e)
            e.printStackTrace();
        
    

class SDFHolder implements Cloneable 
    private SimpleDateFormat sdf;

    public void setSdf(SimpleDateFormat sdf)
        this.sdf = sdf;
    

    public SimpleDateFormat getSdf()
        return this.sdf;
    

    public Object clone() throws CloneNotSupportedException 
        return super.clone();
    


结果是

C:\Users\thangaraj.s\Desktop>java Test
Cloning : 15 ms
Creating : 0 ms

C:\Users\thangaraj.s\Desktop>java Test
Cloning : 16 ms
Creating : 0 ms

C:\Users\thangaraj.s\Desktop>java Test
Cloning : 0 ms
Creating : 15 ms

C:\Users\thangaraj.s\Desktop>java Test
Cloning : 0 ms
Creating : 16 ms

C:\Users\thangaraj.s\Desktop>java Test
Cloning : 16 ms
Creating : 0 ms

因此,我认为这些对性能没有巨大影响,但会根据我的要求提供更简洁的代码。

【讨论】:

【参考方案3】:
public void testPerformance()
    SimpleDateFormat sdf  = new SimpleDateFormat("yyyy-MM-dd");
    long start = System.currentTimeMillis();
    for(int i = 0; i < 1000000; i++)
        SimpleDateFormat localSdf = (SimpleDateFormat)sdf.clone();
    
    System.out.println("Cloning : " + (System.currentTimeMillis() - start) + " ms");

    start = System.currentTimeMillis();
    for(int i = 0; i < 1000000; i++)
        Object localSdf = new SimpleDateFormat("yyyy-MM-dd");
    
    System.out.println("Creating : " + (System.currentTimeMillis() - start) + " ms");


克隆:302 毫秒 创建:885 毫秒

【讨论】:

用户想知道克隆功能背后发生了什么,而不仅仅是性能。 这不是关于对象克隆与构造函数的性能,更多的是关于SimpleDateFormat,因为这个特定的构造函数非常重。我建议使用它的静态实例以供重用。 @kairius static 有一大群反对者将其与全局状态的罪恶混为一谈,因为几乎任何上下文都可能是可重入的。 Final是朋友。 我不是在质疑编程实践,我只是说 SimpleDateFormat 构造函数非常沉重和缓慢。见askldjd.com/2013/03/04/simpledateformat-is-slow【参考方案4】:

约阿希姆是对的。如果你需要复制使用克隆,如果你需要一个单独的对象(对于一个单独的人)你应该使用new并创建一个新的对象。

“更多性能”是主观的,在这里可能不是正确的术语。克隆中发生的是底层对象是共享的,即它们对同一内存位置有 2 个单独的引用。如此有效地节省了创建对象和内存。还记得深拷贝/浅拷贝吗?

【讨论】:

你能解释一下你所说的关于 2 个对象 en 内存具有相同内存位置的事情吗?那么,如果我编辑一个而另一个没有改变,那怎么办?不是有两个独立的内存位置的两个引用吗? 这是一个不错的解释:javarevisited.blogspot.in/2013/09/…【参考方案5】:

如果需要副本,调用 clone(),如果不需要,调用构造函数。 标准的克隆方法 (java.lang.Object.clone()) 无需调用构造函数即可创建对象的浅表副本。如果你需要一个深拷贝,你必须重写 clone 方法。 不用担心性能。性能取决于克隆方法和构造函数的内容,而不是使用的技术(新的或克隆的)本身。

编辑:克隆和构造函数并不是真的可以相互替代,它们满足不同的目的

【讨论】:

那么你的意思是新建一个对象和克隆一个对象有同样的影响? 不,影响取决于构造函数和克隆方法的内容。如果你的类只有几个成员,一个简单的 default-ctor 并且没有覆盖 clone(),may 的影响是相似的。我的建议是仅当您需要现有对象的副本时才使用克隆。克隆和构造函数并不是真的可以相互替代,它们满足不同的目的。 所以如果我想要一个现有对象的克隆,它是否会比使用与我的对象具有相同值的新对象具有更高的性能? 一般无法回答。如果你的类只有基元并且你没有覆盖 clone() 并且你的构造函数处理了一些工作,那么 clone() 会更快maybe。但是这个讨论有点没用,因为你应该使用你需要的技术,而不是最快的或maybe fastest 的技术。构建和克隆是不同的概念,您应该选择适合您需要的技术。 嗯,好吧,但假设在这种情况下我想要最快,克隆会以某种方式让它更快吗?

以上是关于克隆如何比对象创建具有更高的性能的主要内容,如果未能解决你的问题,请参考以下文章

java设计模式——原型模式

从浮点 lat long 创建的 CLLocation 显示更高的精度

使用类似于 Lenet5 的架构对“101 个对象类别”数据进行分类,获得比预期更高的准确度

设计模式-原型模式

创建型设计模式之原型模式

如何在 android 9 上赋予 WiFi 比以太网更高的优先级?