原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
Java当中,提供了Cloneable接口,实现了这个接口表示这个类支持被复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。
举例:
有一份简历,需要被复制成多份,发给不同的人,并且需要对简历的部分内容进行调整。
//简历类
public class Resume1 implements Cloneable {
private String name;
private String sex;
private int age;
private String timeArea;
private String company;
public Resume1(String name) {
this.name = name;
}
// 设置个人信息
public void setPersonalInfo(String sex,int age){
this.sex = sex;
this.age = age;
}
// 设置工作经历
public void setWorkExperience(String timeArea,String company){
this.timeArea = timeArea;
this.company= company;
}
// 展示
public void show(){
System.out.printf("%s %s %s", name, sex, age);
System.out.println();
System.out.printf("工作经历 %s %s", timeArea, company);
System.out.println();
}
// 调用super.clone()方法,可以实现对该类的复制
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return new Resume1("not support clone");
}
}
}
下面是主程序:
//主程序代码
public class Test1 {
public static void main(String[] args) {
Resume1 a = new Resume1("大鸟");
a.setPersonalInfo("男", 24);
a.setWorkExperience("1998-2000", "XX公司");
Resume1 b = (Resume1) a.clone();
b.setWorkExperience("2000-2006", "YY公司");
Resume1 c = (Resume1) a.clone();
c.setPersonalInfo("男", 29);
a.show();
b.show();
c.show();
}
}
运行结果如下:
现在对工作经历进行调整,转化成一个工作经历类,包含工作时间和工作公司:
public class WorkExperience1 {
private String timeArea;
private String company;
public String getTimeArea() {
return timeArea;
}
public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
此时的简历类也做了调整,工作经历部分引用WorkExperience对象。
public class Resume2 implements Cloneable {
private String name;
private String sex;
private int age;
// 调用workExperience对象
WorkExperience workExperience;
public Resume2(String name) {
this.name = name;
workExperience = new WorkExperience();
}
public void setPersonalInfo(String sex,int age){
this.sex = sex;
this.age = age;
}
// 设置工作经历
public void setWorkExperience(String timeArea,String company){
workExperience.setTimeArea(timeArea);
workExperience.setCompany(company);
}
public void show(){
System.out.printf("%s %s %s", name, sex, age);
System.out.printf(" 工作经历 %s %s", workExperience.getTimeArea(), workExperience.getCompany());
System.out.println();
}
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return new Resume2("not support clone");
}
}
}
主程序没有任何变化,为了方便展示结果,显示的时候将个人信息和工作经历显示在了一行。
//主程序代码
public class Test2 {
public static void main(String[] args) {
Resume2 a = new Resume2("大鸟");
a.setPersonalInfo("男", 24);
a.setWorkExperience("1998-2000", "XX公司");
Resume2 b = (Resume2) a.clone();
b.setWorkExperience("2000-2006", "YY公司");
Resume2 c = (Resume2) a.clone();
c.setPersonalInfo("男", 29);
c.setWorkExperience("2006-2010", "ZZ公司");
a.show();
b.show();
c.show();
}
}
运行结果如下:
是不是很奇怪,为什么三份简历的工作经历都变成了最后一份简历的?但是个人信息却和之前clone的结果一样?
继续修改测试类:
//主代码部分
public class Test3 {
public static void main(String[] args) {
Resume2 a = new Resume2("大鸟");
a.setPersonalInfo("男", 24);
a.setWorkExperience("1998-2000", "XX公司");
Resume2 b = (Resume2) a.clone();
b.setWorkExperience("2000-2006", "YY公司");
Resume2 c = (Resume2) a.clone();
c.setPersonalInfo("男", 29);
c.setWorkExperience("2006-2010", "ZZ公司");
a.show();
b.show();
c.show();
System.out.println("a简历和b简历的内存地址一样吗:" + (a == b));
System.out.println("b简历和c简历的内存地址一样吗:" + (b == c));
System.out.println("a简历和c简历的内存地址一样吗:" + (a == c));
System.out.println("a工作经历和b工作经历的内存地址一样吗:" + (a.workExperience == b.workExperience));
System.out.println("b工作经历和c工作经历的内存地址一样吗:" + (b.workExperience == c.workExperience));
System.out.println("a工作经历和c工作经历的内存地址一样吗:" + (a.workExperience == c.workExperience));
}
}
运行结果如下:
发现每一个clone出来的简历对象都是一个新的对象,但是每一份的工作经历对象,都采用的是同一个对象。
这就涉及到了浅复制与深复制的概念:
浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。就是说,每份简历操作的workExperience对象其实都是第一个简历对象中的工作经历对象。
深复制:把引用对象的变量指向复制过来的新对象,而不是原有的被引用对象。
怎么实现深复制呢?看代码:
//工作经历类实现Cloneable接口
public class WorkExperience2 implements Cloneable{
private String timeArea;
private String company;
// 采用clone方法复制,将生成新的对象
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("clone error:" + e.getMessage());
return new WorkExperience2();
}
}
public String getTimeArea() {
return timeArea;
}
public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
public class Resume3 implements Cloneable {
private String name;
private String sex;
private int age;
protected WorkExperience2 workExperience;
public Resume3(String name) {
this.name = name;
this.workExperience = new WorkExperience2();
}
public void setPersonalInfo(String sex, int age) {
this.sex = sex;
this.age = age;
}
public void setWorkExperience(String timeArea, String company) {
workExperience.setTimeArea(timeArea);
workExperience.setCompany(company);
}
public void show() {
System.out.printf("%s %s %s", name, sex, age);
System.out.printf(" 工作经历 %s %s", workExperience.getTimeArea(), workExperience.getCompany());
System.out.println();
}
// 调整原先的clone方法,将workExperience属性也进行复制操作
public Object clone() {
try {
Resume3 obj = (Resume3) super.clone();
obj.workExperience = (WorkExperience2) this.workExperience.clone();
return obj;
} catch (CloneNotSupportedException e) {
System.out.println("clone error:" + e.getMessage());
return new Resume3("error");
}
}
}
下面看主程序代码部分:
//主程序代码
public class Test4 {
public static void main(String[] args) {
Resume3 a = new Resume3("大鸟");
a.setPersonalInfo("男", 24);
a.setWorkExperience("1998-2000", "XX公司");
Resume3 b = (Resume3) a.clone();
b.setWorkExperience("2000-2006", "YY公司");
Resume3 c = (Resume3) a.clone();
c.setPersonalInfo("男", 29);
// c.setWorkExperience("2006-2010", "ZZ公司");
a.show();
b.show();
c.show();
System.out.println("a工作经历和b工作经历的内存地址一样吗:" + (a.workExperience == b.workExperience));
System.out.println("b工作经历和c工作经历的内存地址一样吗:" + (b.workExperience == c.workExperience));
System.out.println("a工作经历和c工作经历的内存地址一样吗:" + (a.workExperience == c.workExperience));
}
}
运行结果如下:
专门注释掉修改c简历的工作经历方法,就是为了更直观的查看工作经历是否都指向相同的内存地址,可以看到,现在各个简历中的工作经历已经和预期一致,而且a简历和c简历中,虽然工作经历相同,但是该对象已经是clone出来的一个新的对象。