原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。用原型实例来指定创建对象的种类,然后通过clone这个原型对象来创建新对象。
先定义一个学生类Student
他实现Cloneable
接口并重写了clone()
方法(clone()方法是Object类中定义的一个方法,重写clone()
方法要实现Cloneable `接口才行):
public class Student implements Cloneable {
private String name; // 姓名
private String className; // 班级名称
public Student(String name) {
super();
this.setName(name);
}
@Override
public Student clone() {
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
// get、set 、toString方法此处省不表
}
这个重写的clone()方法也很简单,就是将克隆的对象返回回来。
利用原型来创建一个新对象:
public class TestPrototype {
public static void main(String[] args) {
Student student = new Student("张三");
student.setClassName("一班");
Student clone = student.clone();
clone.setName("李四");
System.out.println(clone.toString()); // Student [name=李四, className=一班]
}
}
原型模式的根本就体现在这个clone()
方法上,所以要好好看看这个clone()
方法。clone()方法定义在Object类里面:
首先,这个方法会抛出CloneNotSupportedException异常,异常是在重写clone()方法的类没有直接或间接实现Cloneable接口的时候抛出的,所以要重写clone()方法时记得要实现Cloneable接口,顺便说一下Cloneable接口是个空接口里面什么都没定义,他就是起一个标识作用。
第二,通常来说对于任何对象,x.clone() != x这个判断返回的是true(这是肯定的,因为克隆的对象是一个新对象)。并且x.clone().getClass() = = x.getClass() 这个判断返回的是true,但是这个并不是绝对的。然后通常情况下x.clone().equals(x)这个表达式返回的也是true,当然这个也不是绝对的(言外之间就是通常情况下我们重写了clone()方法的时候最好也重写一下equals()方法)。
第三,按惯例,返回的对象应该遵守super.clone
这样的方式来进行克隆,如果一个类或者他的父类遵守这个惯例,那么克隆出来的对象就一定是相同类型的,一定满足x.clone().getClass() == x.getClass() 这个判断。
第四,按惯例,克隆方法返回的对象应该独立于被克隆的对象。为了实现这种独立性,在返回对象之前,可能需要修改super.clone方法返回对象的一个或多个字段。通常来说,这意味着复制包含被克隆对象的内部“深层结构”的任何可变对象,并将对这些对象的引用替换为对副本的引用。如果一个类仅仅只包含基本类型(8种基本类型+string)的字段或不可变对象的引用,那么一般来说super.clone()返回的对旬都不用改变。这么长一短话说白了意思就是:如果被克隆的对象中只包含(8种基本类型+string)或者一些不可变对象的引用,那么clone()方法返回的结果对象中字段值就跟被克隆的对象是一模一样的,如果被克隆对象中包含有可变对象(像List、Map等等)那么你在克隆的时候如果不管他,那克隆返回的对象就中是一个引用指向被克隆对象中的不可变对象,并不会去克隆一个新的可变对象。所以一般来说clone对象中如果包含有可变对象,那么就要在clone()方法里面再克隆一个这个可变对象。
第五,跟第一条说的一样,如果没有实现Cloneable接口就会抛出CloneNotSupportedException 异常。注意,所有数组都认为实现了Cloneable接口,并且数组类型T[]的克隆方法的返回类型是T[],其中T是任何引用或基本数据类型。否则,此方法将创建此对象的类的新实例,并使用该对象的相应字段的内容初始化其所有字段,就像通过赋值一样;字段的内容本身不是克隆的。因此,此方法执行此对象的“浅拷贝”,而不是“深拷贝”操作。
第六,Object 对象本息并没有实现Cloneable接口,所以Object对象上调用clone方法会抛出一个运行时异常。
下面用代码来尝试一下上面所说的6点:
Student
对象不实现Cloneable
接口,重写clone()
方法:public class Student {
private String name; // 姓名
@Override
public Student clone() {
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}
public class TestPrototype {
public static void main(String[] args) {
Student student = new Student();
student.clone();
}
}
结果报错:
java.lang.CloneNotSupportedException: com.qsx.pattern.prototype.entity.Student
at java.lang.Object.clone(Native Method)
Student
对象实现Cloneable
接口,重写clone()
方法和equals()
方法:public class Student implements Cloneable {
private String name; // 姓名
@Override
public Student clone() { // 重写克隆方法
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
@Override
public boolean equals(Object object) { // 重写判断相等方法
if (object instanceof Student) {
Student stu = (Student) object;
if (this.name == stu.name) {
return true;
}
}
return false;
}
// get set 方法省略
}
验证:
public class TestPrototype {
public static void main(String[] args) {
Student student = new Student();
student.setName("aa");
System.out.println(student.clone() != student);
System.out.println(student.clone().getClass() == student.getClass());
System.out.println(student.clone().equals(student));
}
}
三个输出都是true,第一个输出克隆对象跟student对象本来就不是同一个对象所以肯定是true;第二个输出根据上面第三条的说法,克隆方法按惯例这样写super.clone()
所以也是true;第三个输出看到重写的equals()
方法就知道这里肯定是true了。
3. super.clone()
方法对于不同类型的字段克隆的方式不一样,如果是基本数据类型或者string或者数组那么clone就是像赋值一样,这样一来也就不用管其他操作了,直接clone就行了。但是对于那些对象中的所谓“可变对象”像List、Map之类的,那么这个super.clone()
就只是将克隆的新对象中字段的引用指向被克隆的对象中对应的字段中去了,说白了就是克隆对象和被克隆对象会共用这些“可变对象”。举例子:
先定义克隆对象 Student
他有2个“可变对象”作为属性
public class Student implements Cloneable {
private String name; // 姓名
private List parentNames; //可变对象List
private School school; // 可变对象bean
@Override
public Student clone() {
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
public void modifyParent(String name){ // 修改List
this.parentNames.add(name);
}
public void modifySchool(String name,String code){ // 修改bean对象
this.school.setCode(code);
this.school.setName(name);
}
// 省略get set 方法
}
检验结果:
public class TestPrototype {
public static void main(String[] args) {
List pName = new ArrayList<>();
pName.add("father");
pName.add("mother");
School school = new School("小学","0123");
// new 个学生对象 并给2个可变对象赋值
Student s = new Student();
s.setParentNames(pName);
s.setSchool(school);
// 克隆学生对象
Student c = s.clone();
// 修改克隆对象的值
c.modifyParent("Adoptive father");
c.modifySchool("大学", "3210");
System.out.println("被克隆对象的ParentNames:"+s.getParentNames().toString());
System.out.println("被克隆对象的School:"+s.getSchool().toString());
System.out.println("克隆对象的ParentNames:"+c.getParentNames().toString());
System.out.println("克隆对象的School:"+c.getSchool().toString());
System.out.println(s.getSchool().hashCode());
System.out.println(c.getSchool().hashCode());
System.out.println(s.getParentNames().hashCode());
System.out.println(c.getParentNames().hashCode());
}
}
输出结果:
被克隆对象的ParentNames:[father, mother, Adoptive father]
被克隆对象的School:School [name=大学, code=3210]
克隆对象的ParentNames:[father, mother, Adoptive father]
克隆对象的School:School [name=大学, code=3210]
705927765
705927765
-1506096756
-1506096756
根据结果我们可以看到,我们在修改克隆对象的parentNames或者school的时候被克隆对象也一样改了,而下面的hashCode则进一步证明了,实际上克隆对象中的parentNames和school与被克隆对象中的这2个属性是同一个。
这个问题怎么解决呢?我们可以在Student
类的clone()
方法里面手动的去克隆这2个“可变对象”,下面只写clone()
方法的代码,其他代码都一样:
@SuppressWarnings("unchecked")
@Override
public Student clone() {
Student student = null;
try {
student = (Student) super.clone(); // 克隆对象自己
student.school = (School) this.school.clone(); // 克隆对象的school属性
student.parentNames = (List) ((ArrayList) this.parentNames).clone(); // 克隆对象的List属性
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
测试代码不变,结果如下:
被克隆对象的ParentNames:[father, mother]
被克隆对象的School:School [name=小学, code=0123]
克隆对象的ParentNames:[father, mother, Adoptive father]
克隆对象的School:School [name=大学, code=3210]
705927765
366712642
-2144869208
-1506096756
可以看见修改克隆对象属性值的时候并没有修改到被克隆对象的值,而且克隆和被克隆对象中相同属性的hashCode也不一样了。
这里要说明一下的是:Student
中克隆了school
属性和parentNames
属性,但是有个前提不能忘了,School
本身一定要去实现Cloneable
接口,另外就是parentNames
属性的类型是List
,List
是个接口,所以我们只能去找List的接口的实现类中实现了Cloneable
接口的类来进行克隆(如这里的ArrayList
)。