Java基础深度总结:Object类-clone方法

凡心所向,素履所往,生如逆旅,一苇以航。

1.clone概述

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。

protected native Object clone() throws CloneNotSupportedException;
2.Cloneable接口

如果想要使用clone方法,只覆盖Object的clone方法会抛出CloneNotSupportedException异常。

要想正确使用,该对象的类还要实现一个Cloneable标识接口。

public class CloneExample implements Cloneable {

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

Tips:

  • clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。
  • Cloneable接口的特殊之处在于: Cloneable接口中没有任何方法,它存在的意义就是允许其实现类使用clone方法。
public interface Cloneable {
}
3.clone与new的区别
  • new操作符本意实在堆中开辟内存空间。程序执行到new时,会根据new后面的类型来计算分配的内存空间大小,分配完空间以后,调用构造函数填充对象的各个域,然后返回该对象的地址。
  • 在调用clone方法时,程序会分配一块与原对象相同大小的内存空间,然后用原对象的各个域填充到新对象相应的各个域中,然后返回新对象的地址。
  • 由于native方法通常比非native方法更加高效,所以clone方法效率更高
4.浅拷贝与深拷贝(重点)

当被拷贝的对象中包含引用类型的域时,那么对该域有两种拷贝方式:

  • 将原对象中该域的引用值直接拷贝给新对象。
  • 根据原对象中该域所引用的对象,创建一个新的、与之相同的对象,然后将该对象的地址赋值给新对象中该域的引用。

前者就是浅拷贝、后者为深拷贝,例如:

在这里插入图片描述
可以看出来,在浅拷贝中,虽然两个Person对象是不一样的,但是他们的name却引用的同一个对象,这是由于浅拷贝是将引用类型的引用值直接拷贝到新对象中,那么这两个引用必然会指向同一个对象了;而深拷贝是对引用类型进行了递归clone,创建了另一个对象。

Tip:

  • 浅拷贝并没有彻底的将两个对象独立出来
  • 彻底的深拷贝需要将引用链上的所有对象都进行递归clone,比如 body对象引用了head,head对象中又引用了face:
    在这里插入图片描述
5.Object.clone

那么Object中的clone方法是深拷贝还是浅拷贝?答案是:浅拷贝。

public class CloneTest {

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

        Cat cat = new Cat("Tom", 3);
        Cat tom = (Cat) cat.clone();
        System.out.println(cat == tom); //false
        System.out.println(tom.getName() == cat.getName()); // true

        tom.setName("Tom1");
        System.out.println(cat.getName() +"---" + tom.getName()); // Tom --- Tom1
    }
}

class Cat implements Cloneable {

    private String name;
    private int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    public String getName() {
        return name;
    }

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

}

解析:
Cat类中的name是引用类型,如果说原对象和拷贝出来的新对象的name的引用值是相同的,那么就可以说明Object.clone是浅拷贝的,反之代表Object.clone是深拷贝的。从输出结果 System.out.println(tom.getName() == cat.getName()); //true 可以看出来Object.clone确实是浅拷贝。注意: == 比较的是对象的地址。

6.浅拷贝存在的问题

如果对象中包含的域引用了可变的对象,使用Object.clone可能会导致灾难性的后果。比如一个用数组实现的Stack,并且你希望它是可以被clone的。

import java.util.Arrays;

public class ObjectCloneTest{

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

        Stack stack = new Stack();
        Stack stack1 = (Stack) stack.clone();
        stack1.push(1);
        System.out.println(stack.toString());//Stack{data=[1, null, null, null, null, null, null, null, null, null], size=0}
        System.out.println(stack1.toString());//Stack{data=[1, null, null, null, null, null, null, null, null, null], size=1}
    }
}

class Stack implements Cloneable{

    private Object[] data;
    private int size;

    public Stack(){
        data = new Object[10];
        size = 0;
    }

    public void push(Object elem){
        if(size == 10) return;
        data[size] = elem;
        size++;
    }

    public Object pop(){
        if(size == 0) return null;
        size --;
        return data[size];
    }

    public int getSize() {
        return size;
    }

    public Object[] getData() {
        return data;
    }

    @Override
    public String toString() {
        return "Stack{" +
                "data=" + Arrays.toString(data) +
                ", size=" + size +
                '}';
    }

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

当你使用clone出来的对象时,你就会发现很多严重的错误,比如在clone出的对象中添加一个元素,原对象中的size就和数组的实际长度不一致了。这是由于这两个对象的data都引用了同一个数组对象。你应该在data数组中递归地调用clone:

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        Stack cpyStack = (Stack) super.clone();
        cpyStack.data = cpyStack.data.clone();
        return cpyStack;
    }

Tip:
只有可变对象的引用才会出现上述问题,不可变类(IMMUTABLE CLASS)的引用,如String、基本类型的包装类、BigInteger和BigDecimal等,每次对不可变对象的修改都将产生一个新的不可变对象,因此无论修改clone出的对象的可变对象,还是修改原对象中的可变对象,都会导致可变对象的引用值发生变化,而不是可变对象本身发生变化。

例如:Cat示例中修改了 clone出的tom对象中name的值,这会导致tom对象中的name指向新的字符串对象:“Tom1”,而不会影响cat原对象中name所指向的对象。
在这里插入图片描述

7.覆盖clone的规则

由于浅拷贝并不能保证clone出的对象和原对象完全独立,所以在很多时候会导致这样那样的问题,子类覆盖clone一般都是实现深拷贝。

  • 首先调用父类super.clone方法(父类必须实现clone方法),这个方法最终会调用Object中的clone方法完成浅拷贝。
  • 对类中的引用类型进行单独拷贝。
  • 检查clone中是否有不完全拷贝,进行额外的复制。
8.clone的替代方案

使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

  • 拷贝构造器: public Yum(Yum yum);
  • 拷贝工厂: public static Yum newInstance(Yum yum);
  • 使用序列化
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值