现象
复制对象后,如果修改了原对象或新对象的数据,造成了对其他对象的数据也同时发生了变化的现象,就是浅拷贝;对象之间仍然存在关联。
如果复制后的对象与原对象,无论数据如何变化,都不会对其它对象带来变化,就是深拷贝;对象之间已经毫无关系。
1、创建用于实验的类
代码语言:javascript复制class User{
int age;
Name name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@Override
public String toString() {
return "User{"
"age=" age
", name=" name
'}';
}
}
class Name{
String first;
String second;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
@Override
public String toString() {
return "Name{"
"first='" first '''
", second='" second '''
'}';
}
}
2、赋初值
代码语言:javascript复制public class TestDeepClone {
public static void main(String[] args) {
//初始化对象
User user = new User();
user.setAge(25);
Name name = new Name();
name.setFirst("li");
name.setSecond("si");
user.setName(name);
System.out.println("======原始对象=============");
System.out.println(user);
}
}
3、对象赋值
代码片段如下:
代码语言:javascript复制System.out.println("======简单赋值=============");
User user2 = user;
//修改原始对象属性值
user.setAge(27);
user.getName().setFirst("zhang");
System.out.println("源对象:" user);
System.out.println("新对象:" user2);
这种方式,会将原对象的内存地址赋值给新对象,因此,新对象和原对象指向的是同一块内存,如果使用 user == user2, 将会得到 true;
此时如果修改任意一个对象,都将发生变化。
4、浅克隆
如果实现对象克隆,要克隆的类需要实现 Cloneable 接口并重写Object类的 clone()方法,在这里,我们需要对User类进行修改
代码语言:javascript复制class User implements Cloneable{
int age;
Name name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@Override
public String toString() {
return "User{"
"age=" age
", name=" name
'}';
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
在clone()方法中,我们仅对User对象实现了克隆,但是没有对User类下的属性类Name进行克隆;
执行克隆后,新User对象下的Name属性与原对象下的Name属性,仍然指向同一块内存,如果Name属性发生变更,所有克隆对象的Name属性都会变化。
此即为浅克隆。
代码片段如下:
代码语言:javascript复制System.out.println("======浅拷贝=============");
//恢复对象原始值
user.setAge(25);
user.getName().setFirst("li");
//实现对象克隆
User user3 = (User) user.clone();
//修改原始对象属性值
user.setAge(27);
user.getName().setFirst("wang");
System.out.println("源对象:" user);
System.out.println("新对象:" user3);
5、深克隆
5.1 所有对象都实现 Cloneable 接口并重写Object类的 clone()方法
第一步:之前已对User类实现的该接口,这里我们再对User下的对象属性 Name 实现该接口,并重写clone()方法。
代码语言:javascript复制class Name implements Cloneable{
String first;
String second;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
@Override
public String toString() {
return "Name{"
"first='" first '''
", second='" second '''
'}';
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
第二步:修改User类下的clone()方法
代码语言:javascript复制class User implements Cloneable{
int age;
Name name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@Override
public String toString() {
return "User{"
"age=" age
", name=" name
'}';
}
@Override
public Object clone() {
try {
User u = (User) super.clone();
//调用属性的克隆方法
u.setName((Name) this.name.clone());
return u;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
注意:这种方法实现的深克隆比较笨重,如果User类下有多个属性类时,要实现深克隆就需要对所有类重写clone()方法。
测试类的代码片段
代码语言:javascript复制System.out.println("======所有类实现克隆方法的深拷贝=============");
user.setAge(25);
user.getName().setFirst("li");
User user3 = (User) user.clone();
user.setAge(27);
user.getName().setFirst("wang");
System.out.println("源对象:" user);
System.out.println("新对象:" user3);
5.2 使用IO流
要使用IO流,则必须要对拷贝的类及其属性类,实现 Serializable 接口
代码语言:javascript复制class User implements Serializable{
int age;
Name name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
@Override
public String toString() {
return "User{"
"age=" age
", name=" name
'}';
}
}
class Name implements Serializable{
String first;
String second;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
@Override
public String toString() {
return "Name{"
"first='" first '''
", second='" second '''
'}';
}
}
5.2.1 文件IO
通过将对象序列化为字节流,将其写入到文件中,再由文件读取到内存并反序列化为对象,实现对象复制。从而为新对象在内存中开辟出了一份新的区域。
代码语言:javascript复制System.out.println("======IO流实现深拷贝=============");
//恢复原对象属性值
user.setAge(25);
user.getName().setFirst("li");
try {
FileOutputStream fos = new FileOutputStream("d:/cc.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
user.getName().setSecond("二");
FileInputStream fis = new FileInputStream("d:/cc.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User ou = (User) ois.readObject();
System.out.println("源对象:" user);
System.out.println("新对象:" ou);
} catch (Exception e) {
e.printStackTrace();
}
5.2.2 字节数组IO
这种方法是将对象转换为字节输出流后,再将其通过字节输入流写回到内存中,从而达到为新对象在内存中开辟出了一份新区域的目的。
代码语言:javascript复制System.out.println("======字节数组的IO流实现深拷贝=============");
//恢复原对象属性值
user.setAge(25);
user.getName().setFirst("li");
try {
user.getName().setSecond("si");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
user.getName().setSecond("三");
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
User o = (User) ois.readObject();
System.out.println("源对象:" user);
System.out.println("新对象:" o);
} catch (Exception e) {
e.printStackTrace();
}
5.3 借助第三方工具类,通过对象到字节码的转换
此处使用的是阿里开源的 fastjson,将对象转换为字节数组后,再通过json转回对象,达到为新对象在内存中开辟出了一份新区域的目的。
使用该工具类,对要拷贝的类没有侵入性,不需要实现任何接口,一行代码就能搞定。
代码语言:javascript复制System.out.println("======使用第三方工具类实现深拷贝=============");
//恢复原对象属性值
user.setAge(25);
user.getName().setFirst("li");
user.getName().setSecond("si");
User parse = JSON.parseObject(JSON.toJSONBytes(user),User.class);
user.getName().setSecond("四");
System.out.println("源对象:" user);
System.out.println("新对象:" parse);
6、总结:
综上所述,其实要实现深拷贝就两种途径:
A 实现该类及其子类下所有属性类的clone()方法;
B 通过把类转换为底层的字节码,直接从内存层面实现。