概述
-
将一个对象的引用复制给另外一个对象。
-
有三种方式:直接赋值、浅拷贝、深拷贝。
-
这三种概念实际上都是为了拷贝对象。
直接赋值复制
-
直接赋值复制指的是在Java中,使用"="符号进行赋值时,实际复制的是引用而不是值本身。
-
比如,表达式"a1=a2"中赋值给a1的是一个引用地址,即a1和a2都指向同一个对象。
-
因此,如果a1发生变化,与之关联的a2对象中的成员变量也会跟着变化。
深拷贝与浅拷贝
浅拷贝
-
浅拷贝是指在对一个对象进行拷贝时,只拷贝对象本身和其中的基本数据类型,而不拷贝对象内部的引用类型。
-
在Java中,实现浅拷贝可以通过实现Cloneable接口。
-
例如以下代码:Person类包含一个引用类型属性address。当对Person实例original执行浅拷贝并将结果赋给copied后,修改copied中的address属性值时,会发现original中的address属性值也被同步修改。这是因为浅拷贝仅复制了原始对象的引用类型成员变量到新对象中,导致原始对象和新对象的该属性都指向同一内存地址。因此,当原始对象的引用类型成员变量发生改变时,新对象的对应属性也会随之变化
-
class Address { String city; String street; Address(String city, String street) { this.city = city; this.street = street; } } class Person implements Cloneable { String name; int age; Address address; // 引用类型 Person(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); // 使用Object的默认clone方法实现浅拷贝 } } public class ShallowCopyExample { public static void main(String[] args) throws CloneNotSupportedException { Address addr = new Address("New York", "5th Avenue"); Person original = new Person("John", 30, addr); // 执行浅拷贝 Person copied = (Person) original.clone(); // 修改拷贝后的引用类型字段 copied.address.city = "Los Angeles"; // 原始对象的address也被修改了 System.out.println(original.address.city); // 输出: Los Angeles } }
深拷贝
-
在拷贝对象时,不仅复制对象本身及其基本数据类型,还会递归复制对象内部的所有引用类型数据。
-
即克隆后的对象中,其引用类型的变量指向的是全新的地址,而不是被克隆对象中该属性的地址。
-
因此,因此,修改新对象中的引用类型属性时,不会对原对象产生任何影响。
深拷贝方法 | 优点 | 缺点 |
---|---|---|
构造函数 | 1. 底层实现简单 2. 不需要引入第三方包 3. 系统开销小 4. 对拷贝类没有要求,不需要实现额外接口和方法 | 1. 可用性差,每次新增成员变量都需要新增新的拷贝构造函数 |
重载clone()方法 | 1. 底层实现较简单 2. 不需要引入第三方包 3. 系统开销小 | 1. 可用性较差,每次新增成员变量可能需要修改clone()方法 2. 拷贝类(包括其成员变量)需要实现Cloneable接口 |
Serializable序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 | 1. 底层实现较复杂 2. 拷贝类(包括其成员变量)需要实现Serializable接口 3. 序列化与反序列化存在一定的系统开销 |
Gson序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 2. 对拷贝类没有要求,不需要实现额外接口和方法 | 1. 底层实现复杂 2. 需要引入Gson第三方JAR包 3. 序列化与反序列化存在一定的系统开销 |
Jackson序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 2. 对拷贝类没有要求,不需要实现额外接口和方法 | 1. 底层实现复杂 2. 需要引入Jackson第三方JAR包 3. 拷贝类(包括其成员变量)需要实现默认的无参构造函数 4. 序列化与反序列化存在一定的系统开销 |
构造函数实现深拷贝
-
我们可以调用构造函数进行深拷贝
-
形参如果是基本类型和字符串则是直接赋值,如果是对象,则是重新new一个。
public class Address {
String city;
String street;
//构造函数、setter/getter省略
}
public class UserConstruct {
private String userName;
private Address address;//引用类型的属性
//构造函数、setter/getter省略
public static void main(String[] args) {
AddressConstruct address = new AddressConstruct("city", "street");
UserConstruct user = new UserConstruct("小李", address);
// 调用构造函数进行深拷贝
UserConstruct copyUser = new UserConstruct(user.getUserName(), new AddressConstruct(address.getAddress1(), address.getAddress2()));
// 修改源对象中adress属性的值
user.getAdress().getCity().setCity("city2");
// 对比user和copyUser,输出false,因为user中adress属性属性变了,
System.out.println(user == copyUser);
// 对比两个对象中的address属性,同样输出false,因为由于是深拷贝,copyUser中该属性不会受到影响
System.out.println(user.getAddress() == copyUser.getAddress());
}
}
重写clone方法实现深拷贝
public class AddressClone implements Cloneable{
private String address1;
private String address2;
//构造方法、setter/getter省略
/**
* 重写clone方法实现浅拷贝
* @return 克隆后的AddressClone对象
* @throws CloneNotSupportedException 如果对象不支持克隆则抛出异常
*/
@Override
protected AddressClone clone() throws CloneNotSupportedException {
// 调用Object类的native clone方法进行浅拷贝
return (AddressClone) super.clone();
}
}
/**
* 用户类,通过实现Cloneable接口实现深拷贝
*/
public class UserClone implements Cloneable{
private String userName;
private AddressClone address;
//构造方法、setter/getter省略
/**
* 重写clone方法实现深拷贝
* 子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。
* @return 深拷贝后的UserClone对象
* @throws CloneNotSupportedException 如果对象不支持克隆则抛出异常
*/
@Override
protected UserClone clone() throws CloneNotSupportedException {
// 1. 先调用父类的clone方法完成基本类型的浅拷贝
UserClone userClone = (UserClone) super.clone();
// 2. 对引用类型address单独进行克隆,实现深拷贝
userClone.setAddress(this.address.clone());
return userClone;
}
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原始地址对象
AddressClone address = new AddressClone("小区1", "小区2");
// 创建原始用户对象
UserClone user = new UserClone("小李", address);
// 克隆用户对象(深拷贝)
UserClone copyUser = user.clone();
// 修改原始对象的地址
user.getAddress().setAddress1("小区3");
// 测试两个用户对象是否是同一个对象(应该是false)
System.out.println(user == copyUser); // false
// 测试两个用户对象的address1是否相同(应该是false,因为进行了深拷贝)
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1())); // false
}
}
序列化方式实现深拷贝
实现Serializable接口
-
Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象
-
但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。
-
// Person类,实现Serializable接口,表示一个人的信息 class Person implements Serializable { private String name; // 姓名 private Address address; // 地址 //构造方法、setter/getter省略 } // Address类,表示一个地址信息 class Address implements Serializable { private String city; // 城市 // 构造方法、setter/getter省略 } public class Test { // 深拷贝方法,将传入的对象序列化到字节数组中,再从字节数组反序列化为新的对象 private static Object deepCopy(Object obj) throws Exception { // 创建一个字节数组输出流对象,用于存储序列化后的字节数组 ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 创建一个对象输出流对象,用于将对象序列化到字节数组输出流中 ObjectOutputStream oos = new ObjectOutputStream(bos); // 将传入的对象写入对象输出流中,实现对象的序列化 oos.writeObject(obj); // 创建一个字节数组输入流对象,用于读取序列化后的字节数组 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); // 创建一个对象输入流对象,用于将字节数组输入流中的数据反序列化为对象 ObjectInputStream ois = new ObjectInputStream(bis); // 从对象输入流中读取对象,实现对象的反序列化,并返回反序列化后的新对象 return ois.readObject(); } public static void main(String[] args) throws Exception { // 创建一个Address对象,表示北京市的地址 Address address = new Address("Beijing"); // 创建一个Person对象,名为Tom,拥有一个Address对象作为属性 Person person1 = new Person("Tom", address); // 使用深拷贝方法创建一个新的Person对象,与person1对象无关 Person person2 = (Person) deepCopy(person1); // 修改person2中的address属性 person2.setAddress ("shanghai"); // 输出person1和person2是否相等(false),因为它们是不同的对象 System.out.println(person1 == person2); // 输出person1和person2的Address对象是否相等(false),因为它们是不同的对象 System.out.println(person1.getAddress() == person2.getAddress()); } }
Gson序列化方式深拷贝
public class AddressGson {
private String address1;
private String address2;
//构造函数、setter/getter省略
}
/**
* Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝
*/
public class UserGson {
private String userName;
private AddressGson address;
//构造函数、setter/getter省略
public static void main(String[] args) {
AddressGson address = new AddressGson("小区1", "小区2");
UserGson user = new UserGson("小李", address);
// 使用Gson序列化进行深拷贝:通过将源对象序列化成json,然后再反序列化
Gson gson = new Gson();
UserGson copyUser = gson.fromJson(gson.toJson(user), UserGson.class);
user.getAddress().setAddress1("小区3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}
Jackson序列化方式
public class AddressJackson {
private String address1;
private String address2;
//构造函数、setter/getter省略
}
/**
* Jackson与Gson相似,可以将对象序列化成JSON,明显不同的地方是拷贝的类(包括其成员变量)需要有默认的无参构造函数。
*/
@Data
public class UserJackson {
private String userName;
private AddressJackson address;
//需要有默认的无参构造函数
public UserJackson() {
}
public UserJackson(String userName, AddressJackson address) {
this.userName = userName;
this.address = address;
}
public static void main(String[] args) throws JsonProcessingException {
AddressJackson address = new AddressJackson("小区1", "小区2");
UserJackson user = new UserJackson("小李", address);
// 使用Jackson序列化进行深拷贝
ObjectMapper objectMapper = new ObjectMapper();
UserJackson copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), UserJackson.class);
user.getAddress().setAddress1("小区3");
// false
System.out.println(user == copyUser);
// false
System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
}
}