方法一 重載clone()方法
Object父類有個clone()的拷貝方法,不過它是protected類型的,我們需要重寫它並修改為public類型。除此之外,子類還需要實現Cloneable接口來告訴JVM這個類是可以拷貝的。
重寫代碼
讓我們修改一下User類,Address類,實現Cloneable接口,使其支持深拷貝。
/** * 地址 */ public class Address implements Cloneable { private String city; private String country; // constructors, getters and setters @Override public Address clone() throws CloneNotSupportedException { return (Address) super.clone(); } }
/** * 用戶 */ public class User implements Cloneable { private String name; private Address address; // constructors, getters and setters @Override public User clone() throws CloneNotSupportedException { User user = (User) super.clone(); user.setAddress(this.address.clone()); return user; } }
需要注意的是,super.clone()其實是淺拷貝,所以在重寫User類的clone()方法時,address對象需要調用address.clone()重新賦值。
測試用例
@Test public void cloneCopy() throws CloneNotSupportedException { Address address = new Address("杭州", "中國"); User user = new User("大山", address); // 調用clone()方法進行深拷貝 User copyUser = user.clone(); // 修改源對象的值 user.getAddress().setCity("深圳"); // 檢查兩個對象的值不同 assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }
方法二 Apache Commons Lang序列化
Java提供了序列化的能力,我們可以先將源對象進行序列化,再反序列化生成拷貝對象。但是,使用序列化的前提是拷貝的類(包括其成員變量)需要實現Serializable接口。Apache Commons Lang包對Java序列化進行了封裝,我們可以直接使用它。
重寫代碼
讓我們修改一下User類,Address類,實現Serializable接口,使其支持序列化。
/** * 地址 */ public class Address implements Serializable { private String city; private String country; // constructors, getters and setters }
/** * 用戶 */ public class User implements Serializable { private String name; private Address address; // constructors, getters and setters }
測試用例
@Test public void serializableCopy() { Address address = new Address("杭州", "中國"); User user = new User("大山", address); // 使用Apache Commons Lang序列化進行深拷貝 User copyUser = (User) SerializationUtils.clone(user); // 修改源對象的值 user.getAddress().setCity("深圳"); // 檢查兩個對象的值不同 assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }
方法三 Gson序列化
Gson可以將對象序列化成JSON,也可以將JSON反序列化成對象,所以我們可以用它進行深拷貝。
測試用例
@Test public void gsonCopy() { Address address = new Address("杭州", "中國"); User user = new User("大山", address); // 使用Gson序列化進行深拷貝 Gson gson = new Gson(); User copyUser = gson.fromJson(gson.toJson(user), User.class); // 修改源對象的值 user.getAddress().setCity("深圳"); // 檢查兩個對象的值不同 assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }
方法四 Jackson序列化
Jackson與Gson相似,可以將對象序列化成JSON,明顯不同的地方是拷貝的類(包括其成員變量)需要有默認的無參構造函數。
重寫代碼
讓我們修改一下User類,Address類,實現默認的無參構造函數,使其支持Jackson。
/** * 用戶 */ public class User { private String name; private Address address; // constructors, getters and setters public User() { } }
/** * 地址 */ public class Address { private String city; private String country; // constructors, getters and setters public Address() { } }
測試用例
@Test public void jacksonCopy() throws IOException { Address address = new Address("杭州", "中國"); User user = new User("大山", address); // 使用Jackson序列化進行深拷貝 ObjectMapper objectMapper = new ObjectMapper(); User copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), User.class); // 修改源對象的值 user.getAddress().setCity("深圳"); // 檢查兩個對象的值不同 assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }
總結
除了第一種重寫clone方法以外,另外幾種都是通過序列化實現。
方案選擇根據拷貝的類(包括其成員變量)是否提供了深拷貝的構造函數、是否實現了Cloneable接口、是否實現了Serializable接口、是否實現了默認的無參構造函數來進行選擇。如果需要詳細的考慮,則可以參考下面的表格:
|
深拷貝方法
|
優點
|
缺點
|
|
重寫clone()方法
|
1. 底層實現較簡單
2. 不需要引入第三方包
3. 系統開銷小
|
1. 可用性較差,每次新增成員變量可能需要修改clone()方法
2. 拷貝類(包括其成員變量)需要實現Cloneable接口
|
|
Apache.Commons.Lang序列化
|
1. 可用性強,新增成員變量不需要修改拷貝方法
|
1. 底層實現較復雜
2. 需要引入Apache Commons Lang第三方JAR包
3. 拷貝類(包括其成員變量)需要實現Serializable接口
4. 序列化與反序列化存在一定的系統開銷
|
|
Gson序列化
|
1. 可用性強,新增成員變量不需要修改拷貝方法
2. 對拷貝類沒有要求,不需要實現額外接口和方法
|
1. 底層實現復雜
2. 需要引入Gson第三方JAR包
3. 序列化與反序列化存在一定的系統開銷
|
|
Jackson序列化
|
1. 可用性強,新增成員變量不需要修改拷貝方法
|
1. 底層實現復雜
2. 需要引入Jackson第三方JAR包
3. 拷貝類(包括其成員變量)需要實現默認的無參構造函數
4. 序列化與反序列化存在一定的系統開銷
|
