Java的clone()方法详解

Java的clone()方法详解

  • 本文依据“是什么”“做什么”“怎么做”的思路对clone()进行详细讲解
  • 本文目录内容
    1. clone定义与特点
    2. clone()的简单代码实现
    3. 例子讲解(引出clone中的“注意点”)
    4. “浅拷贝”“深拷贝”区别

1、Clone定义特点

  • JDK中的解释为:创建并返回该对象的副本
 Creates and returns a copy of this object.  The precise meaning
 of "copy" may depend on the class of the object. 
  • 下面,总结引申几个特点
    1. 会返回一个对象,副本的类型和原生对象类型相同
    2. 复制,clone()被对象调用,所以会复制对象

其实概念性的东西,大家大可不必深究,对其有了解即可,我们只要懂得应用,而且注意其中的问题就好了

2、简单实现

2.1、实现代码(浅拷贝)

  • 对于clone的实现其实很简单,默认,某些类是不具备克隆能力,所以需要我们进行改造
  • 如何改造呢?换句话说,如何实现clone呢,让类具备克隆能力?
    1. 实现Cloneable接口
    2. 重写clone方法
/**
 * 简单实现拷贝
 */
public class Book implements Cloneable {

    /**
     * 1.实现Cloneable接口
     * 2.重写clone方法
     */

    //重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();//调用父类的clone
    }

}
  • 就是这么简单
  • 就是这么简单
  • 就是这么简单

2.2、简单例子

/**
 * 简单实现clone的例子
 */
public class CloneOriginal implements Cloneable {

    private int i;

    //带参构造方法
    public CloneOriginal(int i) {
        this.i=i;
    }

    //重写clone方法
    @Override
    protected Object clone() {
        Object object = null;
        try {
            object = super.clone();//调用父类的clone
        } catch (CloneNotSupportedException e) {//异常捕获
            e.printStackTrace();
        }
        return object;
    }

    //测试例子
    public static void main(String[] args) {
        CloneOriginal original=new CloneOriginal(10);
        System.out.println("原生的i:"+original.i);

        //由于CloneOriginal实现了Cloneable接口并重写了clone方法,所以当调用CloneOriginal对象的clone方法时,
        //就会克隆出一份CloneOriginal对象的副本,在克隆时,属性和方法均直接copy,
        //由于copy属性时,其属性的类型为int,所以是值传递,copy之后的副本和之前的副本没有任何联系
        //所以在对副本属性i进行++时,自然不会影响到原对象的属性i的值
        CloneOriginal originalCloned=(CloneOriginal)original.clone();
        originalCloned.i++;
        System.out.println("副本的i:"+originalCloned.i);
        System.out.println("原生的i:"+original.i);
    }

}

这里写图片描述

  • 可以发现,对副本属性“i”++后,并不影响原生属性“i”的值,原生属性“i”的值一直为“10”
  • 这说明对副本对象的引用并不影响原生对象,那么,是不是一直都是这样呢?
  • 不是,不是,不是,为什么?
  • 若原生对象的属性中含有引用类型,这样会导致原对象属性的引用和副本对象属性的引用都指向同一个对象,这样当副本对象修改引用类型属的某个属性值时,就间接会影响原对象的引用类型属性的属性值

那么如何才能真正达到两者完全分离呢?这就需要用到:深拷贝和浅拷贝。

3、深拷贝和浅拷贝

3.1、new与clone区别

  • 首先我们先对“new”和“clone”进行概念性的了解

  • Java创建对象方式有两种

    1. new
    2. clone()
  • 那么这两者的不同点在于

new:创建对象的时候,首先会校验new的类型,要根据类型来分配内存空间,分配好内存空间后,再调用构造函数,填充对象的各个属性(域),这一步就叫做对象的初始化,执行完构造函数后,一个对象构建完毕。对象构建完毕后,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操作这个对象。


clone:创建对象的时候,也是先分配内存空间,调用clone()的时候,分配的内存和原生对象(即调用clone方法的对象)相同,然后再使用原生对象中对应的各个属性(域)来填充新对象的域,填充完成后,clone()返回,一个新的对象被创建,同样可以把这个新的对象的引用发布到外部。

3.2、复制引用

这一节就是解释为什么“若原生对象的属性中含有引用类型,则其拷贝的就是句柄,这样会导致原对象属性的引用和副本对象属性的引用都指向同一个对象”?

  • 下面,我们看看如下一段代码的执行情况
public class Book implements Cloneable {

    private int bookId;

    private String bookName;

    /**
     * Book 构造函数
     * 
     * @param bookId
     * @param bookName
     */
    public Book(int bookId, String bookName) {
        super();
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

}
public class TestDemo {

    public static void main(String[] args) {
        Book bookOne=new Book(1, "《Java与模式》");
        Book bookTwo=bookOne;
        System.out.println(bookOne);
        System.out.println(bookTwo);
    }
}

这里写图片描述

  • 可以发现bookOne和bookTwo的内存空间地址是一样的
  • 那么,这说明他们是同一个对象,bookOne和bookTwo只是引用而已,他们都指向了统一个Book对象,new Book(1,”《Java与模式》”)
  • 这种情况就叫做“复制引用”

这里写图片描述

3.3、复制对象(克隆对象)

  • 下面看看克隆一个对象是怎么样的
public class TestDemo {


    public static void main(String[] args) {
        Book bookOne=new Book(1, "《Java与模式》");
        Book bookTwo;

        try {

            bookTwo = (Book)bookOne.clone();

            System.out.println(bookOne);
            System.out.println(bookTwo);

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}

这里写图片描述

  • 不难发现,这两个对象的地址完全不同了,也就是说,这是创建了新的对象
  • 与“复制引用”不同的就是:不再是把原生对像的地址赋给了一个新的引用变量

这里写图片描述

3.4、浅拷贝

  • 我们再看看Book类里面的代码
  • 其实我们上面3.3“复制对象”中实现的就是“浅拷贝”
  • 我们仔细看看“浅拷贝”和“深拷贝”有什么区别?
public class Book implements Cloneable {

    private int bookId;

    private String bookName;

    /**
     * Book 构造函数
     * 
     * @param bookId
     * @param bookName
     */
    public Book(int bookId, String bookName) {
        super();
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

}
  • 从Book类中代码中,我们可以看到,Book有两个成员变量,一个是基本类型int的bookId,一个是引用类型String的bookName;

还记的2.2中提到的:若原生对象的属性中含有引用类型,这样会导致原对象属性的引用和副本对象属性的引用都指向同一个对象,这样当副本对象修改引用类型属的某个属性值时,就间接会影响原对象的引用类型属性的属性值,

  • 下面我们通过图来理解一下
  • 首先bookId是int类型,对它的拷贝,明显这是直接将整数值拷贝,就是拷贝了一个新的,这个是毋庸置疑的,其次,再看看bookName是String类型,它只是一个引用,指向一个真正的String对象,这个我们在“复制引用”和“复制对象”中也有理解,
  • 对于String类型的拷贝有两种方式,就是浅拷贝/深拷贝
    1. 直接将原生对象中bookName的引用值拷贝给新对象的bookName值,
    2. 根据Book对象中bookName指向的字符串对象创建一个新的相同字符串对象,将这个新字符串对象的引用赋给新拷贝的Book对象的bookName成员变量,

这里写图片描述

这里写图片描述

public class TestDemo {

    public static void main(String[] args) {

        try {

            Book bookOne = new Book(1, "《Java与模式》");
            Book bookTwo = (Book) bookOne.clone();

            System.out.println(bookOne);
            System.out.println(bookTwo);

            System.out.println(bookOne.getBookName() == bookTwo.getBookName() ? "浅拷贝" : "深拷贝");

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}

这里写图片描述

  • 如果两个Book对象中的bookName的地址相同,说明是指向同一个String对象,也就是浅拷贝

  • 下面我们再看一个“浅拷贝”的例子,加深理解

public class Picture {

}
public class Artcile {

    public Picture picture;

    public Artcile(){

    }

    public Artcile(Picture picture){
        this.picture=picture;
    }

}
public class Book implements Cloneable {

    //引用对象
    public Artcile artcile;

    public Book(){

    }

    public Book(Artcile artcile){
        this.artcile=artcile;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public static void main(String[] args) {

        try {

            Artcile artcile = new Artcile();

            Book bookOne = new Book(artcile);
            Book bookTwo = (Book) bookOne.clone();

            System.out.println(bookOne);
            System.out.println(bookTwo);

            System.out.println(bookOne.artcile == bookTwo.artcile ? "浅拷贝" : "深拷贝");

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }

这里写图片描述

3.5、深拷贝

  • 实现深拷贝的方法
    1. 对象实现Cloneable接口
    2. 重写clone方法
    3. 并且在clone方法内部,把 该对象 引用的对象 也要clone一份
3.5.1、 不彻底深拷贝
public class Picture {

}
public class Artcile implements Cloneable{

    //引用对象
    public Picture picture;

    public Artcile(){

    }

    public Artcile(Picture picture){
        this.picture=picture;
    }

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

}
public class Book implements Cloneable {

    //引用对象
    public Artcile artcile;

    public Book() {

    }

    public Book(Artcile artcile) {
        this.artcile = artcile;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Book book=(Book)super.clone();
        book.artcile=(Artcile)artcile.clone();//引用对象拷贝
        return book;
    }

}
public static void main(String[] args) {

        try {

            Artcile artcile = new Artcile();

            Book bookOne = new Book(artcile);
            Book bookTwo = (Book) bookOne.clone();

            System.out.println("bookOne与bookTwo是否相同:" + (bookOne == bookTwo));

            System.out.println("article是否相同:" + (bookOne.artcile == bookTwo.artcile));
            System.out.println("picture是否相同:" + (bookOne.artcile.picture == bookTwo.artcile.picture));

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }

这里写图片描述

  • Book中引用了Artice类
  • Artice类中引用了Picture类
  • 在Book类中,拷贝了Artice类
  • 在Artice类中,执行的默认clone(),即是并有没有拷贝Picture类,所以这里默认“浅拷贝”
  • 对于Book而言,拷贝了Artice对象,所以这是“深拷贝”
  • 对于Artice而言,并没有拷贝Picture,所以这是“浅拷贝”
  • 最后导致了“不彻底深拷贝”

这里写图片描述

3.5.2、 彻底深拷贝
public class Picture implements Cloneable{

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

}
public class Artcile implements Cloneable{

    public Picture picture;

    public Artcile(){

    }

    public Artcile(Picture picture){
        this.picture=picture;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        Artcile artcile=(Artcile)super.clone();
        artcile.picture=(Picture)this.picture.clone();
        return artcile;
    }

}

public class Book implements Cloneable {

    // "引用类型"对象
    public Artcile artcile;

    public Book() {

    }

    public Book(Artcile artcile) {
        this.artcile = artcile;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Book book = (Book) super.clone();
        book.artcile = (Artcile) artcile.clone();
        return book;
    }

}
public static void main(String[] args) {

        try {
            Picture picture=new Picture();

            Artcile artcile = new Artcile(picture);

            Book bookOne = new Book(artcile);
            Book bookTwo = (Book) bookOne.clone();

            System.out.println("bookOne与bookTwo是否相同:" + (bookOne == bookTwo));

            System.out.println("article是否相同:" + (bookOne.artcile == bookTwo.artcile));
            System.out.println("picture是否相同:" + (bookOne.artcile.picture == bookTwo.artcile.picture));

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }

这里写图片描述

这里写图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中的Object是所有类的根类,即所有类都直接或间接地继承自Object类。它提供了一些通用方法和功能,可以在所有对象中使用。 以下是一些Object类的常用方法和功能: 1. equals():用于比较两个对象是否相等。默认情况下,它比较的是对象的引用是否相等,但可以根据需要重写equals()方法来改变比较的逻辑。 2. hashCode():返回对象的哈希码。哈希码是根据对象的内部状态计算得到的一个整数,它用于在哈希表等数据结构中快速查找对象。 3. toString():返回对象的字符串表示。默认情况下,它返回对象的类名和哈希码,但可以根据需要重写toString()方法来返回自定义的字符串表示。 4. getClass():返回对象所属的类的Class对象。Class对象包含了有关类的信息,可以用于获取类的属性、方法等信息。 5. finalize():在对象被垃圾回收器回收之前调用。可以重写finalize()方法来执行一些清理操作。 6. clone():创建并返回一个对象的副本。要使用clone()方法,对象所属的类必须实现Cloneable接口。 7. wait()、notify()和notifyAll():用于线程间的协作。wait()方法使当前线程进入等待状态,notify()方法唤醒正在等待的线程,notifyAll()方法唤醒所有正在等待的线程。 此外,Object类还提供了一些其他方法,如getClassLoader()、finalize()、notify()、notifyAll()等。 需要注意的是,Object类中的这些方法都是被声明为final的,即不能被子类重写。这是为了确保这些方法的行为在所有对象中保持一致。 希望以上解答对您有所帮助!如果有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

'嗯哼。

生生不息,“折腾”不止,Thx

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值