什么是对象的克隆?
一种比较官方的解释:需要修改一个对象,同时不想改变调用者的对象,就要制作该对象的一个本地副本。
用我们自己的话来说:把你U盘里面的数据全给我拷贝一份,这样我修改我的的U盘里面的数据,你的U盘里面的数据不会改变(这样是不是变得更容易理解)
在Java中如何实现对象的克隆
其实我们可以思考一个问题,在Java中数据类型分为引用数据类型和基本数据类型。毫无疑问,在对一个对象进行拷贝时,基本数据类型是可以完美复制的(复制的是值),但对一个引用数据类型的属性,我们还能完整复制吗?或者说,这个克隆的新对象包含的引用是否还指向原来的对象?答案当然是肯定的,要不然还有什么可讲的。
由于上述原因,在Java中克隆分为两种方式,一种是浅克隆,一种是深克隆。
浅克隆
如若一个类中只含有基本数据类型,使用Object类中的clone()方法进行克隆就是浅克隆。
看例子:有两个类 Customer 和 Address,其中 ID ,age是基本数据类型,address是引用数据类型
class Customer implements Cloneable{
public int ID;
public int age;
public Address address;
public int getID() {
return ID;
}
public void setID(int iD) {
ID = iD;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Customer(int iD, int age, Address address) {
super();
ID = iD;
this.age = age;
this.address = address;
}
@Override
public String toString() {
return "Customer [ID=" + ID + ", age=" + age + ", address=" + address
+ "]";
}
@Override
public Customer clone() throws CloneNotSupportedException {
return (Customer) super.clone();
}
}
class Address{
private String country;
private String province;
private String city;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address [country=" + country + ", province=" + province
+ ", city=" + city + "]";
}
public Address(String country, String province, String city) {
super();
this.country = country;
this.province = province;
this.city = city;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("CH" , "SD" , "QD");
Customer customer1 = new Customer(1 , 23 , address);
Customer customer2 = customer1.clone();
customer2.getAddress().setCity("JN");
customer2.setID(2);
System.out.println("customer1:"+customer1.toString());
System.out.println("customer2:"+customer2.toString());
}
}
输出结果为
customer1:Customer [ID=1, age=23, address=Address [country=CH, province=SD, city=JN]]
customer2:Customer [ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]
结果分析:
1. 通过分析两个类可知,实现克隆分为两步:先实现 Cloneable 接口,其次是重写 Object中的 clone() 方法,并将clone() 方法的权限修改为 public(Object 中的 clone() 方法时 Protected修饰的,所以不可以直接使用对象调用)
2. 通过结果可知,经过对象的克隆,基本数据类型的数据在修改后不会对原来的对象产生影响,但是引用数据类型数据在修改后原对象的数据也发生改变,想一想这是为什么呢?
解释:在这个例子中,我们只是对Customer这个类使用了clone() 方法(可以将其理解为对象的增强,对谁使用就对谁有效),那么自然克隆的只会是 Customer 这个类的对象,对于 Customer类 中的 Address类型 由于没有经过 clone()方法的克隆,自然没有对其进行完全的复制,只是如同基 本数据类型那样保存了一个引用罢了,在对空间不会有什么改变。
深克隆
既然出现了上面的问题——引用数据类型无法通过浅克隆实现克隆,那么我们有什么解决办法呢,答案就是深克隆(浅的不行就深的,简单粗暴的逻辑)
直接看例子
//1. 在Customer类中重写的clone()方法里对Address进行克隆(进行增强)
@Override
public Customer clone() throws CloneNotSupportedException {
Customer customer = (Customer) super.clone();
customer.address = address.clone();
return customer;
}
//2. 让Address类实现 Cloneable(),并在该类中重写Object的clone() 方法
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
输出结果为
customer1:Customer [ID=1, age=23, address=Address [country=CH, province=SD, city=QD]]
customer2:Customer [ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]
总结
-
深克隆与浅克隆的区别:浅克隆不会克隆原对象中的引用类型,仅仅拷贝了引用类型的指向。深克隆是照单全收,所有都进行了拷贝。
-
使用序列化的方式也可以进行深克隆,但其效率远低于上面所说的方法,但存在必有其意义,如果大家感兴趣可以自己去摸索(提示:上面我讲的是关于一般类类型的克隆,那么对一些特殊的比如List等对象的克隆或许就不能使用这个方法)。
-
要使用clone方法,类的成员变量上不要加final关键字
学到这,对于浅克隆和深克隆我们都有了基本的认识,那么你会不会有什么疑问呢?
请问:我们知道,String,Integer等也是引用数据类型,那么我们需不需要对其进行深克隆操作吗?
这就考验基础了,String,Integer等是线程安全的不可变类(类是用final修饰的,不可变),所以克隆一次,虽说也只是克隆了一个引用,但堆内存会为这个引用开辟一个新的空间来保存新的值。