天天看点

JAVA深克隆和浅克隆

一、基础概念

1. 浅克隆(浅复制)

创建一个新对象,新对象的属性和原来的对象完全相同,对于非基本数据类型属性(即引用类型),扔指向原有属性所指向的对象的内存地址

2. 深克隆(深复制)

创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

二、JAVA的clone()方法

clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足

  1. 对任何的对象x,都有x.clone() !=x  因为克隆对象与原对象不是同一个对象
  2. 对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
  3. 如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立

三、实现方法

浅克隆:

一、通过拷贝构造方法实现浅拷贝:

拷贝构造方法指的是该类的构造方法参数为该类的对象。使用拷贝构造方法可以很好地完成浅拷贝,直接通过一个现有的对象创建出与该对象属性相同的新的对象。

package com.cn.prototype.copy.qian;

public class Age {
	
	private int age;
	
	public Age(int age) {
		this.setAge(age);
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return getAge()+"";
	}
	
}



package com.cn.prototype.copy.qian;

public class Person {
	
	private Age age;  //引用传递
	 
	private String name;  //值传递
	
	public Person(Age age, String name) {
		this.age = age;
		this.name = name;
	}
	
	//构造方法实现浅克隆
	public Person(Person p) {
		this.name = p.name;
		this.age = p.age;
	}
	

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public String toString() {
        return this.name+" "+this.age;
    }
}



package com.cn.prototype.copy.qian;

public class Test {

	public static void main(String[] args) {
		Age age = new Age(19);
		
		Person p1 = new Person(age, "小明");
		Person p2 = new Person(p1);
		
		System.out.println("p1是"+p1);
        System.out.println("p2是"+p2);
        
        //修改p1的各属性值,观察p2的各属性值是否跟随变化
        p1.setName("小红");
        age.setAge(99);
        System.out.println("修改后的p1是"+p1);
        System.out.println("修改后的p2是"+p2);
		
	}

}
           

运行结果:

p1是小明 19

p2是小明 19

修改后的p1是小红 99

修改后的p2是小明 99

结果分析:

通过拷贝构造方法进行了浅拷贝,各属性值成功复制。其中,p1值传递部分的属性值发生变化时,p2不会随之改变;而引用传递部分属性值发生变化时,p2也随之改变。

要注意:如果在拷贝构造方法中,对引用数据类型变量逐一开辟新的内存空间,创建新的对象,也可以实现深拷贝。而对于一般的拷贝构造,则一定是浅拷贝。

二、通过重写clone() 方法进行浅拷贝

Object类是类结构的根类,其中有一个方法为protected Object clone() throws  CloneNotSupportedException,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。但是需要注意:

1、Object类虽然有这个方法,但是这个方法是受保护的(被protected修饰),所以我们无法直接使用。

2、使用clone方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException。对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone方法。

package com.cn.prototype.copy.deep;

public class Age {
	
	private int age;
	
	public Age(int age) {
		this.setAge(age);
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return getAge()+"";
	}
	
}


package com.cn.prototype.copy.deep;

public class Person implements Cloneable {
	
	private Age age;  //引用传递
	 
	private String name;  //值传递
	
	public Person(Age age, String name) {
		this.age = age;
		this.name = name;
	}
	
	@Override
	protected Object clone() {
		Object o = null;
		try {
			o = (Person)super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
				
		return o;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public String toString() {
        return this.name+" "+this.age;
    }
}



package com.cn.prototype.copy.deep;

public class Test {
	
	public static void main(String[] args) {
		Age age = new Age(19);
		
		Person p1 = new Person(age, "小明");
		Person p2 = (Person) p1.clone();
		
		System.out.println("p1是"+p1);
        System.out.println("p2是"+p2);
        
        //修改p1的各属性值,观察p2的各属性值是否跟随变化
        p1.setName("小红");
        age.setAge(99);
        System.out.println("修改后的p1是"+p1);
        System.out.println("修改后的p2是"+p2);
		
	}

}
           

运行结果:

p1是小明 19

p2是小明 19

修改后的p1是小红 99

修改后的p2是小明 99

分析结果可以验证:

基本数据类型是值传递,所以修改值后不会影响另一个对象的该属性值;

引用数据类型是地址传递(引用传递),所以修改值后另一个对象的该属性值会同步被修改。

String类型非常特殊,首先,String类型属于引用数据类型,不属于基本数据类型,但是String类型的数据是存放在常量池中的,也就是无法修改的!也就是说,当我将String类型属性从“a”改为“ab"后,并不是修改了这个数据的值,而是把这个数据的引用从指向”a“这个常量改为了指向”ab“这个常量。在这种情况下,另一个对象的name属性值仍然指向”a“不会受到影响。

深拷贝:

首先介绍对象图的概念。设想一下,一个类有一个对象,其成员变量中又有一个对象,该对象指向另一个对象,另一个对象又指向另一个对象,直到一个确定的实例。这就形成了对象图。那么,对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!

一、通过重写clone方法来实现深拷贝

package com.cn.prototype.copy.deep;

public class Age implements Cloneable{
	
	private int age;
	
	public Age(int age) {
		this.setAge(age);
	}
	
	@Override
	protected Object clone() {
		Object o = null;
		try {
			o = super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return o;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return getAge()+"";
	}
	
}


package com.cn.prototype.copy.deep;

public class Person implements Cloneable {
	
	private Age age;  //引用传递
	 
	private String name;  //值传递
	
	public Person(Age age, String name) {
		this.age = age;
		this.name = name;
	}
	
	@Override
	protected Object clone() {
		Object o = null;
		try {
			o = super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		//把它里面的age对象也进行clone
		Person person = (Person) o;
		person.age = (Age) person.age.clone();
		return o;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public String toString() {
        return this.name+" "+this.age;
    }
}


package com.cn.prototype.copy.deep;

public class Test {
	
	public static void main(String[] args) {
		Age age = new Age(19);
		
		Person p1 = new Person(age, "小明");
		Person p2 = (Person) p1.clone();
		
		System.out.println("p1是"+p1);
        System.out.println("p2是"+p2);
        
        //修改p1的各属性值,观察p2的各属性值是否跟随变化
        p1.setName("小红");
        age.setAge(99);
        System.out.println("修改后的p1是"+p1);
        System.out.println("修改后的p2是"+p2);
		
	}

}
           

结果:

p1是小明 19

p2是小明 19

修改后的p1是小红 99

修改后的p2是小明 19

二、通过对象序列化进行深克隆

虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。

将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

package com.cn.prototype.copy.deep;

import java.io.Serializable;

public class Age implements Serializable{
	
	private static final long serialVersionUID = -7045379962789920819L;
	private int age;
	
	public Age(int age) {
		this.setAge(age);
	}
	
	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return getAge()+"";
	}
	
}



package com.cn.prototype.copy.deep;

import java.io.Serializable;

public class Person implements Serializable {
	
	private static final long serialVersionUID = 2154564168527528525L;

	private Age age;  //引用传递
	 
	private String name;  //值传递
	
	public Person(Age age, String name) {
		this.age = age;
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public String toString() {
        return this.name+" "+this.age;
    }
}



package com.cn.prototype.copy.deep;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {
	
	public static void main(String[] args) throws Exception {
		Age age = new Age(19);
		
		Person p1 = new Person(age, "小明");
		
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(bos);
		oos.writeObject(p1);
		oos.flush();
		
		ObjectInputStream ois=new ObjectInputStream(new 
        ByteArrayInputStream(bos.toByteArray()));
		Person p2 = (Person) ois.readObject();
		
		System.out.println("p1是"+p1);
        System.out.println("p2是"+p2);
        
        //修改p1的各属性值,观察p2的各属性值是否跟随变化
        p1.setName("小红");
        age.setAge(99);
        System.out.println("修改后的p1是"+p1);
        System.out.println("修改后的p2是"+p2);
		
	}

}
           

结果:

p1是小明 19

p2是小明 19

修改后的p1是小红 99

修改后的p2是小明 19