Java实现深克隆的三种方式

大家都知道,Java中的克隆有深克隆和浅克隆,今天我们谈谈深克隆的几种实现方式。

首先,我们先谈谈浅克隆的实现

一、浅克隆

Java中实现浅克隆主要就是要实现Cloneable接口,然后返回克隆对象。

假设,现在我们有两个类,账户类Account和账户详情类AccountDetail,代码如下:

/**
 * 类名 Account
 * 描述 账户类
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Account implements Cloneable{
    /** 主键 */
    private Long id;
    /** 账户名称 */
    private String name;
    /** 账户详情 */
    private AccountDetail detail;
    /** 重写clone方法,改为public方便外部调用 */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}


/**
 * 类名 AccountDetail
 * 描述 账户详情类
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class AccountDetail {
    /** 账户ID */
    private Long accountId;
    /** 邮箱 */
    private String email;
}

账户类引用了账户详情类,如果我们仅仅是浅克隆,那么详情类将不会真正被拷贝,而是拷贝了它的引用。

测试代码:

/**
 * 类名 CloneTest
 * 描述 克隆测试类
 */
public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 浅克隆证明
        AccountDetail detail = new AccountDetail(1L, "[email protected]");
        Account account = new Account(1L, "小何", detail);

        // 克隆
        Account clone = (Account) account.clone();

        // 判断详情对对象是否相同,预期值(true)
        System.out.println(clone.getDetail() == account.getDetail());
    }
}

上述代码执行后,结果为true, 表示原始对象和克隆对象的AccountDetail是同一个 引用。也就是说,该对象没有被克隆。如果修改了AccountDetail对象的值,原始对象和克隆对象都将会发生变化。有时候,这可能并不是我们希望看到的。

所以,我们需要连对象里面的对象也要是一个新的对象。每一个属性都被完全拷贝,这才是深克隆。

 

二、深克隆

1、手动为引用属性赋值

我们修改Account对象的clone方法

@Override
public Object clone() throws CloneNotSupportedException {
    Account account = (Account) super.clone();
    // 手动赋值实现深克隆
    account.setDetail(this.getDetail());
    return account;
}

然后重新运行上述的测试代码,得出的结论为false,也就是原始对象和克隆对象的AccountDetail是不同的对象。也就实现了深克隆。

显然,这种手动的方式在关联对象少的情况是可取的,假设关联的对象里面也包含了对象,就需要层层修改,比较麻烦。不推荐这样使用!

于是,有没有简单的方式呢。当然有,继续往下看!

2、使用fastJson

这种方式我们我们依赖于阿里巴巴的fastJSON包。

public static void main(String[] args) {
    AccountDetail detail = new AccountDetail(1L, "[email protected]");
    Account account = new Account(1L, "小何", detail);

    // fastJson实现克隆
    Account clone = JSONObject.parseObject(JSONObject.toJSONBytes(account), Account.class);

    // 判断详情对对象是否相同,预期值(false)
    System.out.println(clone.getDetail() == account.getDetail());
}

这种方式和很简单 ,也不需要事先Clonable接口 ,不失为一种好的解决方式。

3、使用ObjectStream(推荐)

这就是使用 Java流中的序列化对象事先深克隆。

奉上一个工具类

/**
 * 类名 SerialCloneUtils
 * 描述 序列化克隆工具
 *
 * @author hedonglin
 * @version 1.0
 * @date 2019/11/15 16:36
 */
public class SerialCloneUtils{
    /**
     * 使用ObjectStream序列化实现深克隆
     * @return Object obj
     */
    public static  T deepClone(T t) throws CloneNotSupportedException {
        // 保存对象为字节数组
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            try(ObjectOutputStream out = new ObjectOutputStream(bout)) {
                out.writeObject(t);
            }

            // 从字节数组中读取克隆对象
            try(InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {
                ObjectInputStream in = new ObjectInputStream(bin);
                return (T)(in.readObject());
            }
        }catch (IOException | ClassNotFoundException e){
            CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException();
            e.initCause(cloneNotSupportedException);
            throw cloneNotSupportedException;
        }
    }
}

可以直接通过工具类来实现克隆,跟上面fastJson使用类似,当然也可以再升级一下,提供一个公共的父类,纪要继承该类就可以实现深克隆。

/**
 * 类名 SerialClone
 * 描述 序列化克隆类,只有继承该类,就可以实现深克隆
 *
 */
public class SerialClone implements Cloneable, Serializable {
    private static final long serialVersionUID = 5794148504376232369L;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return SerialCloneUtils.deepClone(this);
    }
}

改造Account类和AccountDetail类,都继承SerialClone。

/**
 * 类名 Account
 * 描述 账户类
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Account /*implements Cloneable*/ extends SerialClone {
    private static final long serialVersionUID = 320551237720888204L;
    /** 主键 */
    private Long id;
    /** 账户名称 */
    private String name;
    /** 账户详情 */
    private AccountDetail detail;

//    @Override
//    public Object clone() throws CloneNotSupportedException {
//        Account account = (Account) super.clone();
//        // 手动赋值实现深克隆
//        account.setDetail(this.getDetail());
//        return account;
//    }
}


/**
 * 类名 AccountDetail
 * 描述 账户详情类
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class AccountDetail extends SerialClone {
    private static final long serialVersionUID = -2232096703114704249L;
    /** 账户ID */
    private Long accountId;
    /** 邮箱 */
    private String email;
}

重新运行测试代码,返回值就已经变成了false。

这种方式,代码虽然多一点,但确实比较高效和容易抽象的,也是很常用的方式!

 

闲暇之余,记录一下代码!码农就是需要不断学习,不进则退,加油!

你可能感兴趣的:(设计模式,设计模式)