这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
在java中,克隆有两种,深克隆和浅克隆。在完成设计模式讲解之后会进行深克隆和浅克隆的学习。
Java提供了一个标记接口——Cloneable,实现该接口完成标记,在JVM中具有这个标记的对象才有可能被拷贝。如果不实现该接口,克隆对象会抛出CloneNotSupportedException异常。
- 原型类 Prototype:声明一个克隆自己的接口。
- 具体原型类 Realizetype:实现原型类的 clone() 方法,实现克隆自己的操作。它是可被复制的对象,可以有多个。
- 访问类 PrototypeTes:使用具体原型类中的 clone() 方法来复制新的对象。
实现:浅克隆和深克隆
若要改变一个对象,但同时不想改变调用者的对象,就要针对该对象制作一个本地副本。这里我们讨论的是对象,如果是基本数据类型,就很简单了,只需要重新定义一个变量名称然后赋值即可。如果是一个对象,可能有些人说了,我直接new一个新的对象就可以了,这确实是一种解决方式,可是有一些在开发中需要使用的对象经过若干逻辑其中的属性早已经不再是初始值了。如果new不行,那么我重新声明一个然后使用=进行赋值,对象如果使用"="赋值,那么两个对象在内存中会指向同一个地址,最终结果就是新对象所做的任何改动都会影响原对象中的值。制作本地副本简单的一种方式就是使用Java自身提供的clone()方法,该方法位于Object类中,权限修饰符是protected。Clone就是克隆的意思,即制作一个一模一样的副本。克隆有最常见的有两种方式即:深克隆和浅克隆,有时也叫做深复制和浅复制。
浅克隆
被克隆对象中所有变量的值都含有和原来对象相同的值,请记住这里所说的是值都是相同的。如果原对象中的变量是基本数据类型的,该变量会复制一份跟克隆对象相同的对象。则会将该引用类型的地址复制一份给克隆对象,原对象和克隆对象中该成员变量的指向相同的内存地址。也就是说如果修改克隆对象中引用类型的值,同样会影响原对象中引用类型的值,反之原对象修改引用类型的值,克隆对象中引用类型值也会跟随着改变。
在Java中通过Object的clone()方法默认实现的就是浅克隆。
例子:
public class Area {
private String country;
private String area;
public Area(String country, String area) {
this.country = country;
this.area = area;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getArea() {
return area;
}
public void setArea(String area) {
this.area = area;
}
}
//实现cloneale
public class Rapper implements Cloneable{
private String name;
// 一个引用
private Area area;
public Rapper(String name, Area area) {
this.name = name;
this.area = area;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Area getArea() {
return area;
}
public void setArea(Area area) {
this.area = area;
}
// 浅克隆重写object方法
@Override
protected Rapper clone() throws CloneNotSupportedException {
return (Rapper) super.clone();
}
@Override
public String toString() {
return "Rapper{" +
"name='" + name + '\'' +
", area=" + area +
'}';
}
}
结果
Rapper{name='王以太', area=com.prototype.Area@4b67cf4d}
Rapper{name='王以太', area=com.prototype.Area@4b67cf4d}
false
true
从运行结果看,我们能看出:
- Rapper类源对象rapper和克隆对象clone虽然内容一致,但两个对象的地址不一致。
- Area类源对象和克隆对象不但内容一样,地址也一样。他們指向同一个地址。
此时如果对clone后的Area类进行修改,源Area也会被修改
public class Main {
public static void main(String[] args) {
Area area = new Area("china", "chongqing");
Rapper rapper = new Rapper("王以太", area);
System.out.println(rapper);
try {
Rapper clone = rapper.clone();
clone.getArea().setRegion("sichuan");
System.out.println(clone);
System.out.println(rapper == clone);
System.out.println(rapper.getArea() == area);
System.out.println(rapper.getArea().getRegion());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
结果
Rapper{name='王以太', area =chongqing}
Rapper{name='王以太', area =sichuan}
false
true
sichuan
如果要解决浅克隆在Area方法中实现Clonable接口并且重写clone方法即可。
public class Area implements Cloneable{
private String country;
@Override
protected Area clone() throws CloneNotSupportedException {
return (Area) super.clone();
}
private String region;
public Area(String country, String region) {
this.country = country;
this.region = region;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
}
//实现cloneale
public class Rapper implements Cloneable{
private String name;
// 一个引用
private Area area;
public Rapper(String name, Area area) {
this.name = name;
this.area = area;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Area getArea() {
return area;
}
public void setArea(Area area) {
this.area = area;
}
// 浅克隆重写object方法
@Override
protected Rapper clone() throws CloneNotSupportedException {
Rapper rapper = (Rapper) super.clone();
rapper.area = rapper.area.clone();
return rapper;
}
@Override
public String toString() {
return "Rapper{" +
"name='" + name + '\'' +
", area =" + area.getRegion() +
'}';
}
}
结果
Rapper{name='王以太', area =chongqing}
Rapper{name='王以太', area =sichuan}
false
true
chongqing
深克隆
序列化方式实现
public class Area implements Serializable {
private String country;
private String region;
public Area(String country, String region) {
this.country = country;
this.region = region;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
}
//实现cloneale
public class Rapper implements Cloneable, Serializable {
private String name;
// 一个引用
private Area area;
public Rapper(String name, Area area) {
this.name = name;
this.area = area;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Area getArea() {
return area;
}
public void setArea(Area area) {
this.area = area;
}
// 浅克隆重写object方法
@Override
protected Rapper clone() throws CloneNotSupportedException {
Rapper rapper = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
rapper = (Rapper) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return rapper;
}
@Override
public String toString() {
return "Rapper{" +
"name='" + name + '\'' +
", area =" + area.getRegion() +
'}';
}
}
结果
Rapper{name='王以太', area =chongqing}
Rapper{name='王以太', area =sichuan}
false
true
chongqing
可以看到,序列化后进行整体克隆并没有因为clone类修改而改变
参考: