Java深度克隆的4種方法


方法一 重載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. 序列化與反序列化存在一定的系統開銷
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM