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