Object类clone方法深度解析

首先我们来看下clone方法的源码

protected native Object clone() throws CloneNotSupportedException;

通过源代码可以发现几点:

  • clone方法是native方法,native方法的效率远高于非native方法,因此还是使用clone方法去做对象的拷贝而不是使用new的方法,copy。
  • 此方法被protected修饰。这就意味着想要使用,必须继承它(废话,默认都是继承的)。然后重载它,如果想要使得其他类能使用这个类,需要设置成public。
  • 返回值是一个Object对象,所以要强制转换才行。

测试clone方法,代码如下:

public class TestReen{
	
	public static void main(String[] args) throws Exception{
		
		TestReen tReen = new TestReen();
		
		TestReen copy = (TestReen)tReen.clone();
	}
	
}

但是运行时发现抛出异常了。

Exception in thread "main" java.lang.CloneNotSupportedException: com.test.count.TestReen
	at java.lang.Object.clone(Native Method)
	at com.test.count.TestReen.main(TestReen.java:11)

 进入clone方法,看注释发现如下信息:表明此类不支持Cloneable接口的话就会报错。

@exception  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.

去看看Cloneable接口,竟然是个空的接口

public interface Cloneable {
}

总结其注释的话,差不多三点:

  • 此类实现了Cloneable接口,以指示Object的clone()方法可以合法地对该类实例进行按字段复制;
  • 如果在没有实现Cloneable接口的实例上调用Object的clone()方法,则会导致抛出CloneNotSupporteddException;
  • 按照惯例,实现此接口的类应该使用公共方法重写Object的clone()方法,Object的clone()方法是一个受保护的方法;

因此想实现clone的话,除了继承Object类外,还需要实现Cloneable接口;

创建并返回此对象的一个副本。对于任何对象x,表达式:

  • x.clone() != x为true
  • x.clone().getClass() == x.getClass()为true
  • x.clone().equals(x)一般情况下为true,但这并不是必须要满足的要求

测试以下例子:

public class TestReen implements Cloneable{
	
	private int id;
	
	private static int i = 1;
	
	public TestReen() {
		System.out.println("i = " + i);
		System.out.println("执行了构造函数"+i);
		i++;
	}
 
	public int getId() {
		return id;
	}
 
	public void setId(int id) {
		this.id = id;
	}
 
	public static void main(String[] args) throws Exception{
		
		TestReen t1 = new TestReen();
		t1.setId(1);
		
		TestReen t2 = (TestReen)t1.clone();
		
		TestReen t3 = new TestReen();
		
		System.out.println("t1 id: "+ t1.getId());
		System.out.println("t2 id: "+ t2.getId());
		System.out.println("t1 == t2 ? " + (t1 == t2));
		System.out.println("t1Class == t2Class ? " + (t1.getClass() == t2.getClass()));
		System.out.println("t1.equals(t2) ? " + t1.equals(t2));
		
	}
}

结果如下,有几个发现:

  • 构造函数除了new执行以外,clone并没有调用到构造函数,也就是clone方法是不走构造方法的。
  • t1 和 t2 是不等的,说明指向了不同的堆地址空间,是两个对象。
  • getClass是相同的,getClass是什么?是获取这个实例的类型类,有点拗口,其实就是TestReen类型,可见clone出来的类型还是一样的。
i = 1
执行了构造函数1
i = 2
执行了构造函数2
t1 id: 1
t2 id: 1
t1 == t2 ? false
t1Class == t2Class ? true
t1.equals(t2) ? false

克隆分为浅克隆(shallow clone)和 深克隆(deep clone)。

  • 浅克隆:Object的clone提供的就是浅克隆,由下面的例子可以看见,只克隆了自身对象和对象内实例变量的地址引用,它内部的实例变量还是指向原先的堆内存区域。
  • 深克隆:克隆所有的对象,包括自身以及自身内部对象。

我们来看下什么是浅克隆,什么是深克隆。例如我有一个Person类:

public class Person implements Cloneable{

    private int age ;
    private String name;

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

    public Person() {}

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

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

 由于age是基本数据类型, 那么对它的拷贝没有什么疑议,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的, 它只是一个引用, 指向一个真正的String对象,那么对它的拷贝有两种方式: 直接将源对象中的name的引用值拷贝给新对象的name字段, 或者是根据原Person对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的name字段。这两种拷贝方式分别叫做浅拷贝和深拷贝。深拷贝和浅拷贝的原理如下图所示:

我们先来看下一个浅克隆的例子:

public class TestReen implements Cloneable{
	private String name;
	private SonReen sonReen;

	public TestReen(String name, SonReen sonReen) {
		this.name = name;
		this.sonReen = sonReen;
	}

	public String getName() {
		return name;
	}

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

	public SonReen getSonReen() {
		return sonReen;
	}

	public void setSonReen(SonReen sonReen) {
		this.sonReen = sonReen;
	}

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

		SonReen sonReen = new SonReen("abc");
		TestReen t1 = new TestReen("李四", sonReen);
		TestReen t2 = (TestReen) t1.clone();
		System.out.println("t1==t2 ? " + (t1 == t2));
		System.out.println("t1.name==t2.name ? " + (t1.getName() == t2.getName()));
		System.out.println("t1.sonReen==t2.sonReen ? " + (t1.getSonReen() == t2.getSonReen()));
		System.out.println("t1.sonReen.SonName==t2.sonReen.SonName ? "
				+ (t1.getSonReen().getSonName() == t2.getSonReen().getSonName()));
		System.out.println("========================================");
		t1.getSonReen().setSonName("王五");
		System.out.println(t1.getSonReen().getSonName());
		System.out.println(t2.getSonReen().getSonName());
	}
}

class SonReen{
	private String sonName;

	public SonReen(String sonName) {
		super();
		this.sonName = sonName;
	}

	public String getSonName() {
		return sonName;
	}

	public void setSonName(String sonName) {
		this.sonName = sonName;
	}
}

结果如下:

t1==t2 ? false
t1.name==t2.name ? true
t1.sonReen==t2.sonReen ? true
t1.sonReen.SonName==t2.sonReen.SonName ? true
========================================
王五
王五

我们从结果看出,只有t1和t2指向的是不同的地址,而t1的sonReen和t2的sonReen指向的是同一块地址,说明浅克隆在克隆一个类的引用类型的成员变量时,克隆的是引用。并且我们修改t1的sonReen的Sonname值的时候,我们发现t2的这个值也被修改了,这更加说明 t1的sonReen和t2的sonReen指向的是同一块地址。

深克隆一般有两种实现方式:

  1. 让类实现序列化接口Serializable。然后对对象进行序列化操作,然后反序列化得到对象。
  2. 先调用super.clone()方法克隆出一个新对象来,然后再调用子对象的clone方法实现深度克隆。

我们先看第一种:


public class TestReen implements Serializable {
	private String name;
	private SonReen sonReen;

	public TestReen(String name, SonReen sonReen) {
		this.name = name;
		this.sonReen = sonReen;
	}

	public String getName() {
		return name;
	}

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

	public SonReen getSonReen() {
		return sonReen;
	}

	public void setSonReen(SonReen sonReen) {
		this.sonReen = sonReen;
	}

	public Object deepClone() throws Exception {

		ByteArrayOutputStream bo = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bo);
		out.writeObject(this);

		ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
		ObjectInputStream oi = new ObjectInputStream(bi);
		return oi.readObject();
	}

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

		SonReen sonReen = new SonReen("abc");
		TestReen t1 = new TestReen("李四", sonReen);
		TestReen t2 = (TestReen) t1.deepClone();
		System.out.println("t1==t2 ? " + (t1 == t2));
		System.out.println("t1.name==t2.name ? " + (t1.getName() == t2.getName()));
		System.out.println("t1.sonReen==t2.sonReen ? " + (t1.getSonReen() == t2.getSonReen()));
		System.out.println("t1.sonReen.SonName==t2.sonReen.SonName ? "
				+ (t1.getSonReen().getSonName() == t2.getSonReen().getSonName()));
		System.out.println("========================================");
		t1.setName("王五");
		System.out.println(t1.getName());
		System.out.println(t2.getName());
	}
}

class SonReen implements Serializable {
	private String sonName;

	public SonReen(String sonName) {
		super();
		this.sonName = sonName;
	}

	public String getSonName() {
		return sonName;
	}

	public void setSonName(String sonName) {
		this.sonName = sonName;
	}
}

结果为:

t1==t2 ? false
t1.name==t2.name ? false
t1.sonReen==t2.sonReen ? false
t1.sonReen.SonName==t2.sonReen.SonName ? false
========================================
王五
李四

我们从结果可以看到,通过序列化和反序列化。克隆出来的t2与t1并不是一个对象,并且t2和t1内部的引用类型的成员变量也得到了克隆,不再是想浅度克隆那样克隆引用。并且我们还可以发现,通过修改t1的name属性,t2的name属性并没有发生变化。更加说明了t1的name和t2的name指向了不同的内存单元。这就是深度克隆。

第二种:

class Body implements Cloneable {
	public Head head;

	public Body() {
	}

	public Body(Head head) {
		this.head = head;
	}

	protected Object clone() throws CloneNotSupportedException {
		Body newBody = (Body) super.clone();
		newBody.head = (Head) head.clone();
		return newBody;
	}
}

class Head implements Cloneable {
	public Head() {
	}

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

public class TestReen {
	public static void main(String[] args) throws CloneNotSupportedException {
		Body body1 = new Body(new Head());
		Body body2 = (Body) body1.clone();
		System.out.println("body1 == body2 : " + (body1 == body2));
		System.out.println("body1.head == body2.head : " + (body1.head == body2.head));

	}
}

结果如下:

body1 == body2 : false
body1.head == body2.head : false

从结果我们看出这也是一个深度克隆,但是要注意的是,如果Head中假设有一个Face类型,如要深度克隆,那么这个Face类型也要实现Cloneable接口并且重写clone方法。

猜你喜欢

转载自blog.csdn.net/qq_39241239/article/details/83475538
今日推荐