深拷貝與淺拷貝
淺拷貝:對(duì)象A進(jìn)行賦值操作得到對(duì)象B,這就是淺拷貝,修改對(duì)象A的屬性會(huì)影響到B的屬性
// 引用類型 sb1調(diào)用自身方法會(huì)影響到sb2,賦值操作就是對(duì)地址的復(fù)制,指向同一個(gè)實(shí)例
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
深拷貝:深拷貝就是希望對(duì)象A和對(duì)象B的操作互不影響。
如何實(shí)現(xiàn)深拷貝
// 對(duì)User的對(duì)象進(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ù)制的對(duì)象
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)嵌套的對(duì)象越來(lái)越多,這種方法顯得繁瑣而且易出錯(cuò)
方法二:使用clone()
既然是復(fù)制,那么可以把User實(shí)例所在的內(nèi)存區(qū)域拷貝一份,然后用新引用指向新區(qū)域,事實(shí)上Java也提供了這樣的操作,即 Object.clone()
進(jìn)行拷貝的類需要實(shí)現(xiàn)Cloneable接口,這是個(gè)標(biāo)記接口,沒(méi)有任何方法,實(shí)現(xiàn)這個(gè)接口的類表示調(diào)用clone()合法。不實(shí)現(xiàn)Cloneable調(diào)用clone()會(huì)拋出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ù)制的對(duì)象
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
這依然是淺拷貝,因?yàn)閡ser實(shí)例內(nèi)存區(qū)域的address對(duì)象依然是個(gè)地址,所以需要對(duì)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ù)制的對(duì)象
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)用,那么有沒(méi)有更方便的方法呢。
方法三:序列化
首先是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ù)制的對(duì)象
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ù)制的對(duì)象
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
原型模式
簡(jiǎn)單來(lái)說(shuō),原型模式就是通過(guò)一個(gè)方法獲得一個(gè)實(shí)例的深拷貝,這里的深拷貝是通過(guò)clone(),具體代碼就是上面的代碼,原型模式很簡(jiǎn)單,主要是理解淺拷貝和深拷貝。
原型模式在Spring中的應(yīng)用
// todo