Java实现深克隆的三种方式

您所在的位置:网站首页 js深拷贝数组的三种实现方式 Java实现深克隆的三种方式

Java实现深克隆的三种方式

2024-01-29 10:12| 来源: 网络整理| 查看: 265

大家都知道,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。

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

 

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3