定义:用于创建重复的对象,同时又能保证性能。当直接创建对象需要很大代价的时候,可以考虑使用该模式。
在Java语言中,所有类的基类Object提供了clone()方法来提供克隆对象功能。但是,衍生类(子类)并不能直接使用它,否则,会报以下异常:
如果我们要使用这个克隆功能,必须实现Cloneable接口,并重写 clone()方法。
类图(针对Java):
下面,我们举个例子说明
1、定义Person抽象类,定义两个属性name和job,并重写clone()方法和toString方法。
/**
* 抽象人类
* @author z_hh
* @date 2018年11月12日
*/
public abstract class Person implements Cloneable {
private String name;
private String job;
public Person(String name, String job) {
super();
this.name = name;
this.job = job;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
/**
* 克隆,调用Object的clone方法,主要是为了处理异常
* @return Object 克隆产生的对象
*/
@Override
protected Object clone() {
Object cloneObj = null;
try {
cloneObj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloneObj;
}
/**
* 重写toString方法
*/
@Override
public String toString() {
return super.toString() + ":[name=" + name + ", job=" + job + "]";
}
}
2、定义Person的两个子类Student和Coder。
/**
* 学生类
* @author z_hh
* @date 2018年11月12日
*/
public class Student extends Person {
public Student(String name, String job) {
super(name, job);
}
}
/**
* 码农类
* @author z_hh
* @date 2018年11月12日
*/
public class Coder extends Person {
public Coder(String name, String job) {
super(name, job);
}
}
3、这样,我们就可以使用Student或者Coder的实例去调用clone()方法了。但是,为了类型强转带来的烦恼,我们写了一个克隆工具类(不过,性能肯定没有直接调用对象的clone()方法好)。
/**
* 克隆工具类
* @author z_hh
* @date 2018年11月12日
*/
public class CloneUtils {
private CloneUtils() {
throw new UnsupportedOperationException("不支持创建实例!");
}
/**
* 获取传入对象的克隆对象
* 只能对实现了Cloneable接口的类使用
* @param obj
* @return 克隆对象
* @throws Throwable
*/
public static <T extends Cloneable> T getCloneObj(T obj) throws Throwable {
// 不能直接调用,会提示The method clone() from the type Object is not visible
// return obj.clone();
// 使用方法句柄(Java7新特性,也可以使用反射)来调用clone方法
MethodHandle methodHandle = MethodHandles.lookup()
.findVirtual(obj.getClass(), "clone", MethodType.methodType(Object.class));
return (T) methodHandle.invoke(obj);
}
}
4、编写测试代码。
public class Test {
public static void main(String[] args) throws Throwable {
Student student = new Student("张三", "学习委员");
Coder coder = new Coder("李四", "项目经理");
// Student cloneStudent = (Student) student.clone();
Student cloneStudent = CloneUtils.getCloneObj(student);
cloneStudent.setJob("班长");
System.out.println(student);
System.out.println(cloneStudent);
System.out.println("------------------------------------");
Coder coder2 = CloneUtils.getCloneObj(coder);
System.out.println(coder);
System.out.println(coder2);
}
}
5、测试结果分析。
从输出内容可以看到:第一,对象的内存地址不一样;第二,修改其中一个对象的属性不会影响到另一个对象 。由此可见,克隆得到的对象引用和原来的对象引用指向不用的对象。
6、创建对象的性能比较。
为了表示直接创建对象消耗大,我们在构造函数里面让其睡眠1毫秒时间。
然后使用三种方式创建1_0000个对象,打印出它们需要使用的时间。
可以看到,使用克隆来获得创建所需代价大的对象(原型模式)显然比直接创建对象的性能好,而克隆工具因为使用了方法句柄(反射)来调用对象的clone()方法,也一定程度上影响了性能。