深拷貝與淺拷貝
淺拷貝:對象A進(jìn)行賦值操作得到對象B蟆盹,這就是淺拷貝日缨,修改對象A的屬性會影響到B的屬性
// 引用類型 sb1調(diào)用自身方法會影響到sb2掖看,賦值操作就是對地址的復(fù)制,指向同一個實例
StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = sb1;
sb1.append(" world");
System.out.println(sb1.toString()); // hello world
System.out.println(sb2.toString()); // hello world
深拷貝:深拷貝就是希望對象A和對象B的操作互不影響毅待。
如何實現(xiàn)深拷貝
// 對User的對象進(jìn)行深拷貝
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Address address;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Address {
String province;
String city;
}
方法一:使用new
// 被復(fù)制的對象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 new 深拷貝
Address addressCopy = new Address(address.getProvince(), address.getCity());
User userCopy = new User(user.getName(), addressCopy);
當(dāng)嵌套的對象越來越多吱涉,這種方法顯得繁瑣而且易出錯
方法二:使用clone()
既然是復(fù)制外里,那么可以把User實例所在的內(nèi)存區(qū)域拷貝一份盅蝗,然后用新引用指向新區(qū)域,事實上Java也提供了這樣的操作芙委,即 Object.clone()
進(jìn)行拷貝的類需要實現(xiàn)Cloneable
接口灌侣,這是個標(biāo)記接口裂问,沒有任何方法堪簿,實現(xiàn)這個接口的類表示調(diào)用clone()
合法戴甩。不實現(xiàn)Cloneable
調(diào)用clone()
會拋出CloneNotSupportedException
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Cloneable{
private String name;
private Address address;
@Override
protected User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Address {
String province;
String city;
}
// 被復(fù)制的對象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 clone() 深拷貝
User userCopy = user.clone();
// 檢查
user.getAddress().setCity("handan");
System.out.println(userCopy.getAddress().getCity()); // handan
這依然是淺拷貝甜孤,因為user實例內(nèi)存區(qū)域的address對象依然是個地址缴川,所以需要對address進(jìn)行拷貝把夸。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Cloneable {
private String name;
private Address address;
// change
@Override
protected User clone() throws CloneNotSupportedException {
User user = (User) super.clone();
Address address = user.getAddress().clone();
user.setAddress(address);
return user;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Address implements Cloneable {
String province;
String city;
// change
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
// 被復(fù)制的對象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 clone() 深拷貝
User userCopy = user.clone();
// 檢查
user.getAddress().setCity("handan");
System.out.println(userCopy.getAddress().getCity()); // zhangjiakou
這樣在調(diào)用上比new優(yōu)雅許多铭污,但在clone()
里面也需要注意嵌套調(diào)用膀篮,那么有沒有更方便的方法呢誓竿。
方法三:序列化
首先是JAVA自帶的序列化功能
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private String name;
private Address address;
// change
public User deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (User) ois.readObject();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Address implements Serializable {
String province;
String city;
}
// 被復(fù)制的對象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 Serialize 深拷貝 change
User userCopy = user.deepClone();
// 檢查
user.getAddress().setCity("handan");
System.out.println(userCopy.getAddress().getCity()); // zhangjiakou
使用JSON序列化也可以
// 被復(fù)制的對象
Address address = new Address("hebei", "zhangjiakou");
User user = new User("zhang", address);
// 使用 JSON序列化 深拷貝
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
User userCopy = mapper.readValue(json, User.class);
// 檢查
user.getAddress().setCity("handan");
System.out.println(userCopy.getAddress().getCity()); // zhangjiakou
原型模式
簡單來說,原型模式就是通過一個方法獲得一個實例的深拷貝簸喂,這里的深拷貝是通過clone()
喻鳄,具體代碼就是上面的代碼诽表,原型模式很簡單,主要是理解淺拷貝和深拷貝袄简。
原型模式在Spring中的應(yīng)用
// todo