克隆
java中实现克隆的方式有两种,一种是实现Cloneable接口,这种方式会存在深拷贝和浅拷贝的问题。另一种是实现Serializable接口,通过序列化的方式进行克隆,反序列话会生成崭新的对象。
实现Cloneable接口
浅拷贝(Object类中的clone()方法)是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝。
深拷贝(或叫深克隆) 则是对对象及该对象关联的对象内容,都会进行一份拷贝。
接下来用具体实例来看深拷贝和浅拷贝的区别。
实体类People:
package com.wangzhen.clone.shallow_clone; public class People implements Cloneable { private String name; private int age; public People(String name, int age) { this.name = name; this.age = age; } //省略get和set方法 @Override public String toString() { return "People [name=" + name + ", age=" + age + "]"; } @Override protected Object clone() { People people = null; try { people = (People) super.clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return people; } public static void main(String[] args) { People people = new People("wisdom",18); People clonePeople = (People) people.clone(); System.out.println(people); //输出: People [name=wisdom, age=18] System.out.println(clonePeople); //输出: People [name=wisdom, age=18] people.setName("Klay"); System.out.println(people);// People [name=Klay, age=18] System.out.println(clonePeople);// 输出: People [name=wisdom, age=18] } }
我们将people称为原对象,将clonePeople称为克隆对象。以上例子看到,当原对象中的成员变量改变时,不会影响到克隆对象,这说明克隆对象中的成员变量是一份属于自己“私有”的数据。但是当原对象中含有对其他对象的引用时,情况就有所不同。如下例子:
实体类Address:
package com.wangzhen.clone.shallow_clone; public class Address { private String addr; public Address(String addr) { this.addr = addr; } //省略get和set方法 @Override public String toString() { return "Address [addr=" + addr + "]"; } }
实体类People02:
package com.wangzhen.clone.shallow_clone; public class People02 implements Cloneable { private String name; private int age; private Address addr; public People02(String name, int age, Address addr) { this.name = name; this.age = age; this.addr = addr; } //省略get和set方法 @Override public String toString() { return "People02 [name=" + name + ", age=" + age + ", addr=" + addr + "]"; } @Override protected Object clone() { People02 people = null; try { people = (People02) super.clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return people; } public static void main(String[] args) { Address addr = new Address("123456789@qq.com"); People02 people02 = new People02("wisdom", 18, addr); People02 clonePeople02 = (People02) people02.clone(); System.out.println(people02); // 输出: People02 [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]] System.out.println(clonePeople02); // 输出: People02 [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]] people02.setName("Klay"); people02.getAddr().setAddr("987654321@qq.com"); System.out.println(people02);// 输出: People02 [name=Klay, age=18, addr=Address [addr=987654321@qq.com]] System.out.println(clonePeople02);// 输出: People02 [name=wisdom, age=18, addr=Address [addr=987654321@qq.com]] } }
我在People02类中引用了一个Address对象,由以上输出可知,原对象改变成员变量时,克隆对象的成员变量不会发生任何改变,而当我改变原对象的引用对象的值时,克隆对象也随之发生改变,这说明clone方法生成的克隆对象对于成员变量是重新生成一份属于自己私有的,而对于引用变量则还是指向原对象的引用。
以上就是浅引用,那么如何实现深引用呢,需要在Address类中也实现Cloneable接口,重写clone方法。并且在People02类中的clone方法中改变address类的引用。
改变后的Address类:
package com.wangzhen.clone.shallow_clone; public class Address implements Cloneable { private String addr; public Address(String addr) { this.addr = addr; } //省略get和set方法 @Override public String toString() { return "Address [addr=" + addr + "]"; } @Override protected Object clone(){ People02 people02 = null; try { people02 = (People02) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return people02; } }
People02类:
package com.wangzhen.clone.shallow_clone; public class People02 implements Cloneable { private String name; private int age; private Address addr; public People02(String name, int age, Address addr) { this.name = name; this.age = age; this.addr = addr; } //省略get和set方法 @Override public String toString() { return "People02 [name=" + name + ", age=" + age + ", addr=" + addr + "]"; } @Override protected Object clone() { People02 people = null; try { people = (People02) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } people.addr = (Address) addr.clone(); return people; } public static void main(String[] args) { Address addr = new Address("123456789@qq.com"); People02 people02 = new People02("wisdom", 18, addr); People02 clonePeople02 = (People02) people02.clone(); System.out.println(people02); // 输出: People02 [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]] System.out.println(clonePeople02); // 输出: People02 [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]] people02.setName("Klay"); people02.getAddr().setAddr("987654321@qq.com"); System.out.println(people02);// 输出: People02 [name=Klay, age=18, addr=Address [addr=987654321@qq.com]] System.out.println(clonePeople02);// 输出: [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]] } }
以上输出可以看到,此时的克隆对象已经和原对象没有什么关系了,对原对象做任何的修改也不会影响到克隆对象。这边是深克隆。
但是在实际应用场景中,如果一个类中存在对其他对象的引用,这个引用对象还可能引用了别的对象,造成嵌套引用。这种情况下如果使用上面实现Cloneable的方式,那简直是要爆炸。
为了解决上面嵌套引用的问题,可以通过实现Serializable接口的方式,通过序列化和反序列化来达到克隆一个全新对象的目的。
实现Serializable接口
- 实现Serializable接口在开发中是一种常用的方式,在javaee开发中需要所有的pojo类实现此接口,目的就是为了能够使对象在网络上得到传输。
Address类:
package com.wangzhen.clone.serializable;
import java.io.Serializable;
/**
* Address类,People类中会引用到此类,需实现Serializable接口
*/
public class Address implements Serializable {
private String addr;
public Address(String addr) {
this.addr = addr;
}
//省略get和set方法
@Override
public String toString() {
return "Address [addr=" + addr + "]";
}
}
People类:
package com.wangzhen.clone.serializable;
import java.io.Serializable;
/**
* people类,需要实现Serializable接口
*/
public class People implements Serializable {
/** serialVersionUID */
private static final long serialVersionUID = 1L;
private String name;
private int age;
private Address addr;
public People(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
//省略get和set方法
@Override
public String toString() {
return "People [name=" + name + ", age=" + age + ", addr=" + addr + "]";
}
}
克隆类:
package com.wangzhen.clone.serializable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 通过序列化反序列化实现对象的克隆
*/
public class CloneObj {
@SuppressWarnings("unchecked")
public static <T> T cloneObj(T obj) {
T cloneObj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
// System.out.println("序列化完毕");
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
cloneObj = (T) ois.readObject();
// System.out.println("反序列化完毕");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return cloneObj;
}
}
测试类:
package com.wangzhen.clone.serializable;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
Address addr = new Address("123456789@qq.com");
People people = new People("wisdom", 18, addr);
People cloneObj = CloneObj.cloneObj(people);
System.out.println(people);// 输出:People [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
System.out.println(cloneObj);// 输出:People [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
// 对people对象中的addr进行修改
people.getAddr().setAddr("987654321@qq.com");
System.out.println(people);// 输出:People [name=wisdom, age=18, addr=Address [addr=987654321@qq.com]]
System.out.println(cloneObj);// 输出:People [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
// 比较原对象和克隆对象以及各自的addr引用是否为同一引用
System.out.println(people == cloneObj);// 输出:false
System.out.println(people.getAddr() == cloneObj.getAddr());// 输出:false
}
}
通过输出可以得知,克隆对象的所有数据都属于自己“私有”,包括值对象还是引用对象。
注:如有错误请及时指正,感谢~