一、基础概念
1. 浅克隆(浅复制)
创建一个新对象,新对象的属性和原来的对象完全相同,对于非基本数据类型属性(即引用类型),扔指向原有属性所指向的对象的内存地址
2. 深克隆(深复制)
创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
二、JAVA的clone()方法
clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足
- 对任何的对象x,都有x.clone() !=x 因为克隆对象与原对象不是同一个对象
- 对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
- 如果对象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