资讯专栏INFORMATION COLUMN

从clone方法到复制构造函数

孙吉亮 / 2380人阅读

摘要:有一些设计缺陷,其中最大的一个是接口没有方法。这基本上就是你用复制构造函数做的事情。复制构造方法有几个优点,我在本书中有讨论。的方法是非常棘手的。它创建一个对象而不调用构造函数。无法保证它保留构造函数建立的不变量。

前言

在Java API中,可以通过实现Cloneable接口并重写clone方法实现克隆,但Java设计者否定了使用clone创建新对象的方法.

1. clone方法实现对象的复制

在Java API中,如果被克隆的对象成员变量有对象变量,则对象变量也需要实现Cloneable接口,并重新给新的父类赋值,例如:

1.创建一个对象,其存在对象类型的成员变量childClone

class ParentsClone implements Cloneable {

    public int id;
    public ChildClone childClone;

    public ParentsClone(int id) {
        this.id = id;
    }

    public ParentsClone(int id, ChildClone childClone) {
        this.id = id;
        this.childClone = childClone;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

2.因此ChildClone也需要实现Cloneable:

class ChildClone implements Cloneable {
    public String name;

    public ChildClone(String name) {
        this.name = name;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

3.所以要做到真正的clone需要按照如下调用顺序操作:

public class ParentClone implements Cloneable {

    public static void main(String[] args) throws CloneNotSupportedException {
        ParentsClone p1 = new ParentsClone(1, new ChildClone("HAHA"));
        ParentsClone p2 = (ParentsClone) p1.clone();
        p2.childClone = (ChildClone) p1.childClone.clone();

        System.out.println("p1 HashCode: " + p1.hashCode() + "  p1.child HashCode: " + p1.childClone.hashCode());
        System.out.println("p2 HashCode: " + p2.hashCode() + "  p2.child HashCode: " + p2.childClone.hashCode());
    }
}
//output:
/**
* p1 HashCode: 1163157884  p1.child HashCode: 1956725890
* p2 HashCode: 356573597  p2.child HashCode: 1735600054
*/

4.如此便完成了java对象的真正clone.但是java开发者并不建议这样做.

2. 和JAVA开发者对话

Bill Venners: 在你的书中,你建议使用复制构造函数而不是实现Cloneable和编写clone。你能详细说明吗?

Josh Bloch:如果你已经阅读了我的书中关于克隆的章节,特别是如果你看得仔细的话,你就会知道我认为克隆已经完全坏掉的东西。有一些设计缺陷,其中最大的一个是 Cloneable 接口没有 clone方法。这意味着它根本不起作用:实现了 Cloneable 接口并不说明你可以用它做什么。相反,它说明了内部可能做些什么。它说如果通过super.clone 反复调用它最终调用 Object 的 clone 方法,这个方法将返回原始的属性副本。

但它没有说明你可以用一个实现 Cloneable 接口的对象做什么,这意味着你不能做多态 clone 操作。如果我有一个 Cloneable 数组,你会认为我可以运行该数组并克隆每个元素以制作数组的深层副本,但不能。你不能强制转换对象为 Cloneable 接口并调用 clone 方法,因为 Cloneable 没有public clone 方法,Object 类也没有。如果您尝试强制转换 Cloneable 并调用该 clone 方法,编译器会说您正在尝试在对象上调用受保护的clone方法。

事实的真相是,您不通过实施 Cloneable 和提供 clone 除复制能力之外的公共方法为您的客户提供任何能力。如果您提供具有不同名称的copy操作, 怎么也不次于去实现 Cloneable 接口。这基本上就是你用复制构造函数做的事情。复制构造方法有几个优点,我在本书中有讨论。一个很大的优点是可以使副本具有与原始副本不同的实现。例如,您可以将一个 LinkedList 复制到 ArrayList。

Object 的 clone 方法是非常棘手的。它基于属性复制,而且是“超语言”。它创建一个对象而不调用构造函数。无法保证它保留构造函数建立的不变量。多年来,在Sun内外存在许多错误,这源于这样一个事实,即如果你只是super.clone 反复调用链直到你克隆了一个对象,那么你就拥有了一个浅层的对象副本。克隆通常与正在克隆的对象共享状态。如果该状态是可变的,则您没有两个独立的对象。如果您修改一个,另一个也会更改。突然之间,你会得到随机行为。

我使用的东西很少实现 Cloneable。我经常提供实现类的 clone 公共方法,仅是因为人们期望有。我没有抽象类实现 Cloneable,也没有接口扩展它,因为我不会将实现的负担 Cloneable 放在扩展(或实现)抽象类(或接口)的所有类上。这是一个真正的负担,几乎没有什么好处。

Doug Lea 走得更远。他告诉我 clone 除了复制数组之外他不再使用了。您应该使用 clone 复制数组,因为这通常是最快的方法。但 Doug 的类根本就不再实施 Cloneable了。他放弃了。而且我认为这并非不合理。

这是一个耻辱, Cloneable 接口坏掉了,但它发生了。最初的 Java API在紧迫的期限内完成,以满足市场窗口收紧的需求。最初的 Java 团队做了不可思议的工作,但并非所有的 API 都是完美的。 Cloneable 是一个弱点,我认为人们应该意识到它的局限性。
传送门:<复制构造函数与克隆>英文原版

3. 总结Java开发者的话

设计缺陷, Cloneable 接口没有 clone方法.

调用的是Object的clone方法,返回原始的属性副本.

clone方法返回浅层的对象副本.

应该使用 clone 复制数,因为通常速度最快..{具体使用: int[] newArrays=(int[])oldArrays.clone() }

复制构造方法的优点:副本具有与原始副本不同的实现.

4. 复制构造函数

java开发者不建议我们使用clone方法,从而转向灵活的构造函数实现方法.
1.新写两个类,分别两个构造函数,关注第二个构造函数,参数为当前类的对象实例.

class Parents {

    public int id;
    public Child child;

    public Parents(int id, Child child) {
        this.id = id;
        this.child = child;
    }
    //实现对象的复制
    public Parents(Parents parents) {
        id = parents.id;
        child = new Child(parents.child);
    }
}


class Child {
    public String name;

    public Child(String name) {
        this.name = name;
    }
    //实现对象的复制
    public Child(Child child) {
        name = child.name;
    }
}

2.测试:

public static void main(String[] args) throws CloneNotSupportedException {

       Parents p1=new Parents(1,new Child("HAHA"));
       Parents p2=new Parents(p1);

       System.out.println("p1 HashCode: " + p1.hashCode() + "  p1.child HashCode: " + p1.child.hashCode());
       System.out.println("p2 HashCode: " + p2.hashCode() + "  p2.child HashCode: " + p2.child.hashCode());

       //output
       /**
        * p1 HashCode: 1163157884  p1.child HashCode: 1956725890
        * p2 HashCode: 356573597  p2.child HashCode: 1735600054
        */

   }
结语

以上便是笔者对放弃clone,使用构造方法创建新对象的整理.以后的程序中尽量还是多用复制构造函数的方法.若有不足,敬请指正.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73949.html

相关文章

  • Java 作者谈克隆方法的实现

    摘要:不合规的代码示例合规解决方案参阅复制构造函数与克隆也可以参阅应该实现克隆覆盖的类应为并调用下面为引文翻译谈设计与作者的对话,作者首次在上发表,年月日复制构造函数与克隆在你的书中,你建议使用复制构造函数而不是实现和编写。 今天在用 sonar 审核代码, 偶然看到下面的提示:showImg(https://segmentfault.com/img/bVbqioZ?w=858&h=116)...

    gaomysion 评论0 收藏0
  • [译] 为什么原型继承很重要

    摘要:使用构造函数的原型继承相比使用原型的原型继承更加复杂,我们先看看使用原型的原型继承上面的代码很容易理解。相反的,使用构造函数的原型继承像下面这样当然,构造函数的方式更简单。 五天之前我写了一个关于ES6标准中Class的文章。在里面我介绍了如何用现有的Javascript来模拟类并且介绍了ES6中类的用法,其实它只是一个语法糖。感谢Om Shakar以及Javascript Room中...

    xiao7cn 评论0 收藏0
  • 搞定PHP面试 - PHP魔术方法知识点整理

    摘要:魔术方法知识点整理代码使用语法编写一构造函数和析构函数构造函数具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。在析构函数中调用将会中止其余关闭操作的运行。析构函数中抛异常会导致致命错误。 PHP魔术方法知识点整理 代码使用PHP7.2语法编写 一、构造函数和析构函数 __construct() 构造函数 __construct ([ mi...

    付永刚 评论0 收藏0
  • 设计模式之原型模式

    摘要:但是这种复制技术在的世界里早已出现,就是原型模式什么是原型模式用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象类图原型模式是设计模式中最简单的,没有之一。 前言 在现实世界中,我们通常会感觉到分身乏术。要是自己有分身那该多好啊,一个用来工作,一个用来看电视,一个用来玩游戏(无意中透露了自己单身狗的身份-。-),其实就是克隆,这种技术存在着很大的弊端,所以现在是禁止使用的。...

    jsyzchen 评论0 收藏0
  • JavaScript 原型链

    摘要:命令通过构造函数新建实例对象,实质就是将实例对象的原型,指向构造函数的属性,然后在实例对象上执行构造函数。 大部分面向对象的编程语言,都是以类(class)作为对象体系的语法基础。JavaScript语言中是没有class的概念的(ES6之前,ES6中虽然提供了class的写法,但实现原理并不是传统的类class概念,仅仅是一种写法), 但是它依旧可以实现面向对象的编程,这就是通过Ja...

    verano 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<