原型模式 一:原型模式概述
为什么要用原型模式:
在系统中有时候可能需要创建多个一模一样的对象,而有的对象创建过程十分复杂,或者创建对象很耗费资源亦或是创建对象十分频繁,那么这个时候就必须要解决这个问题,而原型模式则能很好的解决这个问题。
基本定义:
原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。
二:原型模式原理结构图基本角色
Prototype(抽象原型类)
是声明了克隆方法的接口,所有具体原型类的基类,既可以是接口又可以是抽象类,还可以是具体实现类。
ConcretePrototype(具体原型类)
实现抽象原型类中的克隆方法,在克隆方法中返回自己一个克隆对象。
浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所拷贝的对象,而不复制它所引用的对象。
深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
实现java深复制和浅复制的最关键的就是要实现Cloneable接口中的clone()方法。
如何使用clone()方法首先我们来看一下Cloneable接口:
官方解释:
1:实现此接口则可以使用java.lang.Object 的clone()方法,否则会抛出CloneNotSupportedException 异常
2:实现此接口的类应该使用公共方法覆盖clone方法
3:此接口并不包含clone 方法,所以实现此接口并不能克隆对象,这只是一个前提,还需覆盖上面所讲的clone方法。
public interface Cloneable { }看看Object里面的Clone()方法:
clone()方法返回的是Object类型,所以必须强制转换得到克隆后的类型
clone()方法是一个native方法,而native的效率远远高于非native方法,
可以发现clone方法被一个Protected修饰,所以可以知道必须继承Object类才能使用,而Object类是所有类的基类,也就是说所有的类都可以使用clone方法
protected native Object clone() throws CloneNotSupportedException;小试牛刀:
public class Person { public void testClone(){ super.clone(); // 报错了 } }事实却是clone()方法报错了,那么肯定奇怪了,既然Object是一切类的基类,并且clone的方法是Protected的,那应该是可以通过super.clone()方法去调用的,然而事实却是会抛出CloneNotSupportedException异常, 官方解释如下:
对象的类不支持Cloneable接口
覆盖方法的子类也可以抛出此异常表示无法克隆实例。
所以我们更改代码如下:
public class Person implements Cloneable{ public void testClone(){ try { super.clone(); System.out.println("克隆成功"); } catch (CloneNotSupportedException e) { System.out.println("克隆失败"); e.printStackTrace(); } } public static void main(String[] args) { Person p = new Person(); p.testClone(); } }要注意,必须将克隆方法写在try-catch块中,因为clone方法会把异常抛出,当然程序也要求我们try-catch。
java.lang.object规范中对clone方法的约定对任何的对象x,都有x.clone() !=x 因为克隆对象与原对象不是同一个对象
对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立
对于以上三点要注意,这3项约定并没有强制执行,所以如果用户不遵循此约定,那么将会构造出不正确的克隆对象,所以根据effective java的建议:
谨慎的使用clone方法,或者尽量避免使用。
浅复制实例对象中全部是基本类型
public class Teacher implements Cloneable{ private String name; private int age; public Teacher(String name, int age){ this.name = name; this.age = age; } // 覆盖 @Override public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } // 客户端测试 public class test { Teacher origin = new Teacher("tony", 11); System.out.println(origin.getName()); Teacher clone = (Teacher) origin.clone(); clone.setName("clone"); System.out.println(origin.getName()); System.out.println(clone.getName()); }结果:
tony
tony
clone
从运行结果和图上可以知道,克隆后的值变量会开辟新的内存地址,克隆对象修改值不会影响原来对象。