浅克隆和深克隆
我们在《Java原型设计模式》一节介绍了,在 Java 提供的 API 中,不需要手动创建抽象原型接口。Java 中的 Object 类提供了浅克隆的 clone() 方法。因为 Java 已经内置了 Cloneable 抽象原型接口,自定义的类型只需实现该接口并重写 Object.clone() 方法就可实现对象的浅克隆。
通过查看 JDK 的源码可以发现,Cloneable 是一个空接口,如下。
一般情况下,使用 clone() 方法需要满足以下条件。
我们在设计自定义类的 clone() 方法时,应当遵守这 3 个条件。一般来说,这 3 个条件中的前 2 个是必需的,第 3 个是可选的。
下面使用 Java 提供的 API 应用来实现原型模式,代码如下。
在日常开发中,使用 super.clone() 方法并不能满足所有需求。如果类中存在引用对象属性,则原型对象与克隆对象的该属性会指向同一对象的引用。添加 ConcretePrototype 类实现 Cloneable 接口,代码如下。
从测试结果来看,应该是 hobbies 共用了一个内存地址,意味着复制的不是值,而是引用的地址。这样的话,如果我们修改任意一个对象中的属性值,protoType 和 cloneType 的 hobbies 值都会改变。这就是我们常说的浅克隆,只是完整复制了值类型数据,没有复制引用对象。换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。那如何解决这个问题呢?
Java 自带的 clone() 方法进行的就是浅克隆。如果我们想进行深克隆,可以直接在 super.clone() 后,手动给克隆对象的相关属性分配另一块内存,不过如果当原型对象维护很多引用属性的时候,手动分配会比较烦琐。因此,在 Java 中,如果想完成原型对象的深克隆,通常使用序列化(Serializable)的方式。
通过查看 JDK 的源码可以发现,Cloneable 是一个空接口,如下。
public interface Cloneable { }Java 之所以提供 Cloneable 接口,只是为了在运行时通知 Java 虚拟机可以安全地在该类上使用 clone() 方法。而如果该类没有实现 Cloneable 接口,那么调用 clone() 方法会抛出 CloneNotSupportedException 异常。
一般情况下,使用 clone() 方法需要满足以下条件。
- 对任何对象 o,都有 o.clone() != o。换言之,克隆对象与原型对象不是同一个对象。
- 对任何对象 o,都有 o.clone().getClass() == o.getClass()。换言之,克隆对象与原型对象的类型一样。
- 如果对象 o 的 equals() 方法定义恰当,则 o.clone().equals(o) 应当成立。
我们在设计自定义类的 clone() 方法时,应当遵守这 3 个条件。一般来说,这 3 个条件中的前 2 个是必需的,第 3 个是可选的。
下面使用 Java 提供的 API 应用来实现原型模式,代码如下。
public class Client { public static void main(String[] args) { //创建原型对象 ConcretePrototype type = new ConcretePrototype("original"); System.out.println(type); //复制原型对象 ConcretePrototype cloneType = null; try { cloneType = type.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } cloneType.desc = "clone"; System.out.println(cloneType); } static class ConcretePrototype implements Cloneable { private String desc; public ConcretePrototype(String desc) { this.desc = desc; } @Override protected ConcretePrototype clone() throws CloneNotSupportedException { ConcretePrototype cloneType = null; try { cloneType = (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return cloneType; } @Override public String toString() { return "ConcretePrototype{" + "desc='" + desc + '\'' + '}'; } } }运行结果如下:
ConcretePrototype{desc='original'}
ConcretePrototype{desc='clone'}
在日常开发中,使用 super.clone() 方法并不能满足所有需求。如果类中存在引用对象属性,则原型对象与克隆对象的该属性会指向同一对象的引用。添加 ConcretePrototype 类实现 Cloneable 接口,代码如下。
@Data public class ConcretePrototype implements Cloneable { private int age; private String name; private List<String> hobbies; @Override protected ConcretePrototype clone() { try { return (ConcretePrototype) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ",name='" + name + '\'' + ",hobbies=" + hobbies + '}'; } }修改客户端测试代码。
public class Client { public static void main(String[] args) { //创建原型对象 ConcretePrototype type = new ConcretePrototype(); type.setAge(18); type.setName("新宝库"); List<String> hobbies = new ArrayList<String>(); hobbies.add("书法"); hobbies.add("美术"); type.setHobbies(hobbies); //拷贝原型对象 ConcretePrototype cloneType = type.clone(); cloneType.getHobbies().add("技术控"); System.out.println("原型对象:" + type); System.out.println("克隆对象:" + cloneType); } }运行结果如下:
原型对象:ConcretePrototype{age=18,name='新宝库',hobbies=[书法, 美术, 技术控]}
克隆对象:ConcretePrototype{age=18,name='新宝库',hobbies=[书法, 美术, 技术控]}
从测试结果来看,应该是 hobbies 共用了一个内存地址,意味着复制的不是值,而是引用的地址。这样的话,如果我们修改任意一个对象中的属性值,protoType 和 cloneType 的 hobbies 值都会改变。这就是我们常说的浅克隆,只是完整复制了值类型数据,没有复制引用对象。换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。那如何解决这个问题呢?
Java 自带的 clone() 方法进行的就是浅克隆。如果我们想进行深克隆,可以直接在 super.clone() 后,手动给克隆对象的相关属性分配另一块内存,不过如果当原型对象维护很多引用属性的时候,手动分配会比较烦琐。因此,在 Java 中,如果想完成原型对象的深克隆,通常使用序列化(Serializable)的方式。
使用序列化实现深克隆
在上面的基础上继续改造,增加一个 deepClone() 方法。public class ConcretePrototype implements Cloneable,Serializable { private int age; private String name; private List<String> hobbies; @Override public ConcretePrototype clone() { try { return (ConcretePrototype)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } public ConcretePrototype deepCloneHobbies(){ try { ConcretePrototype result = (ConcretePrototype)super.clone(); result.hobbies = (List)((ArrayList)result.hobbies).clone(); return result; } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } public ConcretePrototype deepClone(){ try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (ConcretePrototype)ois.readObject(); }catch (Exception e){ e.printStackTrace(); return null; } } @Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }客户端调用代码如下:
public static void main(String[] args) { //创建原型对象 ConcretePrototype prototype = new ConcretePrototype(); prototype.setAge(18); prototype.setName("新宝库"); List<String> hobbies = new ArrayList<String>(); hobbies.add("书法"); hobbies.add("美术"); prototype.setHobbies(hobbies); //拷贝原型对象 ConcretePrototype cloneType = prototype.deepCloneHobbies(); cloneType.getHobbies().add("技术控"); System.out.println("原型对象:" + prototype); System.out.println("克隆对象:" + cloneType); System.out.println(prototype == cloneType); System.out.println("原型对象的爱好:" + prototype.getHobbies()); System.out.println("克隆对象的爱好:" + cloneType.getHobbies()); System.out.println(prototype.getHobbies() == cloneType.getHobbies()); }运行结果如下,与期望的结果一致。
原型对象:ConcretePrototype{age=18, name='新宝库', hobbies=[书法, 美术]}
克隆对象:ConcretePrototype{age=18, name='新宝库', hobbies=[书法, 美术, 技术控]}
false
原型对象的爱好:[书法, 美术]
克隆对象的爱好:[书法, 美术, 技术控]
false
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算