一、简介
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。通熟一点就是对象的复制或克隆,在实现过程中我们也会用到Cloneable这个接口。原型模式根据实现方式的不同,分为浅拷贝和深拷贝.
- 浅拷贝:对象拷贝时,只拷贝基本数据类型(String,Integer等)变量,不拷贝引用类型变量。
- 深拷贝:相对于浅拷贝,除了基本数据类型变量外,还拷贝引用类型变量。但引用类型变量里还存在新的引用类型,需要拷贝到哪一级需要根据需求自己判断。
克隆说白了就是对象的复制,复制后的对象和原对象应该存在以下关系条件:
- 对任何对象x,都有:x.clone()!=x。克隆对象与原对象不是同一个对象。
- 对任何对象x,都有:x.clone().getClass() == x.getClass(),克隆对象与原对象的类型一样。
- 对任何对象x,x.clone().equals(x) 不一定为true。
- 对任何对象x,都有:x.clone() 字段数据和x字段数据是"相同"的,注意这个相同打了引号,只是数值层面上的相同。
注意:equals() 方法默认是和 == 相同的,这说明在不重写equals()方法的情况下,默认只要两个对象引用地址相同,equals()方法就会返回true。如果重写了equals(),那就只能取决于实现方式了。
优点:
- 可以简化创建过程,避开构造函数的约束,同时也能够提高效率。
- 可以使用深克隆保持原型对象的数据。
- 原型模式提供了简化的创建结构。
缺点:
- 深度克隆的时候可能代码会比较复杂。
- 深度克隆需要对每个类进行处理。
二、浅克隆
假如现有一个复杂数据对象,我们需要在原数据的基础上做一些小的修改,而且还需要能查看原数据对象的数据,如果是new是不是很痛苦,你还得把原数据对象的数据一个一个的set到新的对象,但是如果我们直接clone呢,是不是so easy。下面来试试:
有两个类,Team和Person,Team引用Person列表,都继承Cloneable且重写简单的clone方法
public class Team implements Cloneable {
private Integer id;
private String name;
private Person leader;
private List<Person> members;
public Team(Integer id, String name, Person leader, List<Person> members) {
this.id = id;
this.name = name;
this.leader = leader;
this.members = members;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getLeader() {
return leader;
}
public void setLeader(Person leader) {
this.leader = leader;
}
public List<Person> getMembers() {
return members;
}
public void setMembers(List<Person> members) {
this.members = members;
}
@Override
public Team clone() {
try {
return (Team) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class Team implements Cloneable {
private Integer id;
private String name;
private Person leader;
private List<Person> members;
public Team(Integer id, String name, Person leader, List<Person> members) {
this.id = id;
this.name = name;
this.leader = leader;
this.members = members;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getLeader() {
return leader;
}
public void setLeader(Person leader) {
this.leader = leader;
}
public List<Person> getMembers() {
return members;
}
public void setMembers(List<Person> members) {
this.members = members;
}
@Override
protected Person clone() {
try {
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
/**
* 创建新的Team对象并初始化数据
*
* @return
*/
private static Team createOriginalPerson() {
Person leader = new Person("Original Team Leader");
List<Person> members = new ArrayList<Person>();
members.add(new Person("Original Team Member 1"));
Team originalTeam = new Team(1, "Original Team", leader, members);
return originalTeam;
}
/**
* 打印对比结果
* @param originalTeam
* @param cloneTeam
*/
private static void printCompare(Team originalTeam, Team cloneTeam) {
System.out.println("cloneTeam == originalTeam:" + (cloneTeam == originalTeam));
System.out.println("cloneTeam.getId() == originalTeam.getId():" + (cloneTeam.getId() == originalTeam.getId()));
System.out.println("cloneTeam.getName() == originalTeam.getName():" + (cloneTeam.getName() == originalTeam.getName()));
System.out.println("cloneTeam.getLeader() == originalTeam.getLeader():" + (cloneTeam.getLeader() == originalTeam.getLeader()));
System.out.println("cloneTeam.getMembers() == originalTeam.getMembers():" + (cloneTeam.getMembers() == originalTeam.getMembers()));
}
接下来我们创建一个Team对象,并初始化一些数据,然后再从这个对象clone一个新对象,注意文明调用的是简单的clone方法没有进行重写,看看会得到怎样一个新对象:
Team originalTeam = createOriginalPerson();
//super.clone() 浅复制
Team cloneTeam = originalTeam.clone();
printCompare(originalTeam,cloneTeam);
输出结果:
cloneTeam == originalTeam:false
cloneTeam.getId() == originalTeam.getId():true
cloneTeam.getName() == originalTeam.getName():true
cloneTeam.getLeader() == originalTeam.getLeader():true
cloneTeam.getMembers() == originalTeam.getMembers():true
cloneTeam.getTags() == originalTeam.getTags():true
从输出结果我们可以看出,cloneTeam == originalTeam:false表示通过clone()之后,确实生成了两个不同的对象,对象里面的引用似乎还是原来的对象。我们再来做进一步的验证一下是否是燕来的引用对象,修改cloneTeam引用对象的值,看看originalTeam里面引用(字段)对象的值是否发生变化,如果是同一个引用对象,修改cloneTeam中引用对象的值,originalTeam中引用对象的值肯定会发生变化,如果不是则不会发生变化。修改cloneTeam的值:
/**
* 修改克隆对象的值
*
* @param cloneTeam
*/
private static void modifyCloneTeam(Team cloneTeam) {
List<Person> cloneTeamMembers = cloneTeam.getMembers();
Person cloneTeamLeader = cloneTeam.getLeader();
String[] tags = cloneTeam.getTags();
//修改原始数据,对比cloneTeam和originalTeam的数据
cloneTeam.setId(2);
cloneTeam.setName("Clone Team");
cloneTeamLeader.setName("Clone Team Leader");
//修改cloneTeamMembers
cloneTeamMembers.add(new Person("Clone Team new member"));
cloneTeamMembers.get(0).setName("Clone Team Member");
tags[0] = "Clone Tag 1";
}
public static void main(String[] args) {
System.out.println("==============Simple clone start===============");
//Init data
Team originalTeam = createOriginalPerson();
//输出原始数据
//super.clone() 浅复制
Team cloneTeam = originalTeam.clone();
printCompare(originalTeam, cloneTeam);
System.out.println("Original team :" + originalTeam);
//修改原生数据
modifyCloneTeam(cloneTeam);
//输出修改后的数据
System.out.println("After modify clone team, Original Team:" + originalTeam);
System.out.println("After modify clone team, Clone Team:" + cloneTeam);
System.out.println("==============Simple clone end===============\n\n");
}
输出结果:
==============Simple clone start===============
cloneTeam == originalTeam:false
cloneTeam.getId() == originalTeam.getId():true
cloneTeam.getName() == originalTeam.getName():true
cloneTeam.getLeader() == originalTeam.getLeader():true
cloneTeam.getMembers() == originalTeam.getMembers():true
cloneTeam.getTags() == originalTeam.getTags():true
Original team :Team{id=1, name='Original Team', tags=[Original Tag 1], leader=Person{name='Original Team Leader'}, members=[Person{name='Original Team Member 1'}]}
After modify clone team, Original Team:Team{id=1, name='Original Team', tags=[Clone Tag 1], leader=Person{name='Clone Team Leader'}, members=[Person{name='Clone Team Member'}, Person{name='Clone Team new member'}]}
After modify clone team, Clone Team:Team{id=2, name='Clone Team', tags=[Clone Tag 1], leader=Person{name='Clone Team Leader'}, members=[Person{name='Clone Team Member'}, Person{name='Clone Team new member'}]}
==============Simple clone end===============
在代码中,修改了cloneTeam中每个引用的值,在List<Person>引用变量中,不仅修改了原有的Person对象的值,还新增了一个新对象,对比一下数据,在修改cloneTeam对象后,原对象originalTeam中除字段id和name之外的数据都发生了变化,变成cloneTeam修改之后的数据了,为什么呢,再看看字段数据类型,id和name分别是Integer和String类型(基础数据类型)其它的字段都是引用数据类型,注意tags字段是String[]也属于引用数据类型。
结论:浅克隆(也就是直接调用Object的clone()方法)得到的克隆对象中,能克隆(产生新的对象)的只有基本数据类型,而引用数据类型不会被克隆,克隆新生成的对象中继续引用源对象里的对象。
思考:为什么基本数据类型 cloneTeam.getName() == originalTeam.getName()返回的是true呢,这就要从Java中数据的存储方式说起,Java中基本数据类型存储在栈中,而引用数据类型存储在堆中,引用数据类型的引用也存储在栈中。如果基本数据类型如:int a =3; int b = 3;指向的是同一个地址,所以返回true。更深入的了解,自行Google吧。
三、深克隆
继续上面的问题,我们使用浅克隆的结果是可能有多个对象同时引用同一个引用变量,如果其中一个对象修改了引用变量中的值,其它变量的值也会受到影响,在某些情况下这显然不满足我们的需求,那么怎样才能满足我们的需求呢,那么我们就需要进行深克隆。深克隆需要我们重写clone()方法,这里便于区分,我使用deepClone()替代。
首先,我们重写Team的clone 方法:
Team.java
/**
* 深度克隆,只是demo,暂时没做非空判断,优秀的代码,是需要时刻考虑异常情况的
*
* @return
*/
public Team deepClone() {
try {
Team team = (Team) super.clone();
Person cloneLeader = leader.clone();
team.setLeader(cloneLeader);
List<Person> cloneMembers = new ArrayList<Person>();
for (Person person : members) {
cloneMembers.add(person.clone());
}
team.setMembers(cloneMembers);
String[] cloneTags = new String[tags.length];
System.arraycopy(tags, 0, cloneTags, 0, tags.length);
team.setTags(cloneTags);
return team;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
同之前操作,继续修改cloneTeam参数,并打印输出:
public static void main(String[] args) {
//Init data
Team originalTeam = createOriginalPerson();
//输出原始数据
System.out.println("==============Deep clone start===============");
//super.clone() 浅复制
Team cloneTeam = originalTeam.deepclone();
printCompare(originalTeam, cloneTeam);
System.out.println("Original team :" + originalTeam);
//修改原生数据
modifyCloneTeam(cloneTeam);
//输出修改后的数据
System.out.println("After modify clone team, Original Team:" + originalTeam);
System.out.println("After modify clone team, Clone Team:" + cloneTeam);
System.out.println("==============Deep clone end===============\n\n");
}
输出结果为:
==============Deep clone start===============
cloneTeam == originalTeam:false
cloneTeam.getId() == originalTeam.getId():true
cloneTeam.getName() == originalTeam.getName():true
cloneTeam.getLeader() == originalTeam.getLeader():false
cloneTeam.getMembers() == originalTeam.getMembers():false
cloneTeam.getTags() == originalTeam.getTags():false
Original team :Team{id=1, name='Original Team', tags=[Original Tag 1], leader=Person{name='Original Team Leader'}, members=[Person{name='Original Team Member 1'}]}
After modify clone team, Original Team:Team{id=1, name='Original Team', tags=[Original Tag 1], leader=Person{name='Original Team Leader'}, members=[Person{name='Original Team Member 1'}]}
After modify clone team, Clone Team:Team{id=2, name='Clone Team', tags=[Clone Tag 1], leader=Person{name='Clone Team Leader'}, members=[Person{name='Clone Team Member'}, Person{name='Clone Team new member'}]}
==============Deep clone end===============
由此可见,在我们重写了clone()之后,引用对象的 == 都为false了,修改cloneTeam的数据后,也不会再影响originalTeam的数据了。其实从代码里面也可以看出,在deepClone()中,我们相当于为每个引用类型的字段重新创建了一个对象,只是考虑到字段庞大的情况下,我们还是继续采用clone()来处理,不适用new,如果你适用new还是能达到同样的效果的,但是如果字段多的话,工作量可想而知。同理,如果在引用对象Person中又存在其它的引用对象呢?也需要同样的操作。
结论:深克隆能保证源对象和克隆对象之间数据的独立性,但是需要引用类型字段做单独的克隆处理,代码复杂度也会随之增加。
最后,附上完整的Demo源码地址供大家参考