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. 序列化与反序列化存在一定的系统开销
 
posted @ 2021-12-31 11:40  baissy  阅读(4762)  评论(0编辑  收藏  举报