原型模式:通过给出一个原型对象来指明所创建的对象的类型,然后使用自身实现的克隆接口来复制这个原型对象,该模式就是用这种方式来创建出更多同类型的对象。
实现:Object类的clone方法可以将一个对象复制一份,需要实现clone方法的java类必须实现Cloneable接口,该接口表示该类能够复制且具备复制的能力。Object 类的 clone 方法是一个本地方法,它可以直接操作内存中的二进制流,所以性能相对 new 实例化来说,更佳。
一个对象通过new创建的过程为:
1、在内存中开辟一块空间;
2、在开辟的内存空间中创建对象;
3、调用对象的构造函数进行初始化对象。
而一个对象通过clone创建的过程为:
1、根据原对象内存大小开辟一块内存空间;
2、复制已有对象,克隆对象中所有属性值。
相对new来说,clone少了调用构造函数。如果构造函数中存在大量属性初始化或大对象,则使用clone的复制对象的方式性能会好一些。
实现原型类的条件
- 实现Cloneable接口;Cloneable 接口与序列化接口的作用类似,它只是告诉虚拟机可以安全地在实现了这个接口的类上使用 clone 方法。在 JVM 中,只有实现了 Cloneable 接口的类才可以被拷贝,否则会抛出 CloneNotSupportedException 异常。
- 重写 Object 类中的 clone 方法:在 Java 中,所有类的父类都是 Object 类,而 Object 类中有一个 clone 方法,作用是返回对象的一个拷贝
- 在重写的 clone 方法中调用 super.clone():默认情况下,类不具备复制对象的能力,需要调用 super.clone() 来实现。
/**
* 实现Cloneable接口的原型抽象类Prototype
*/
public class Prototype implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* 实现原型类
*/
public class ConcretePrototype extends Prototype {
public ConcretePrototype(){
System.out.println("原型模式实现类构造方法");
}
public void show(){
System.out.println("原型模式实现类show方法");
}
}
public class Client {
public static void main(String[] args) throws Exception{
ConcretePrototype cp = new ConcretePrototype();
for(int i = 0; i < 10; i++){
ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
clonecp.show();
}
}
}
--输出结果
原型模式实现类构造方法
原型模式实现类show方法
原型模式实现类show方法
原型模式实现类show方法
原型模式实现类show方法
原型模式实现类show方法
原型模式实现类show方法
原型模式实现类show方法
原型模式实现类show方法
原型模式实现类show方法
原型模式实现类show方法
从结果可以看出,Prototype的构造方法只调用了一次。
原型模式与普通对象引用复制的区别
普通对象引用复制(Object o1 = new Object();Object o2 = o1)
public class Student {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student();
student1.setName("张三");
Student student2 = student1;
student2.setName("李四");
System.out.println("学生1的name:" + student1.getName());
System.out.println("学生2的name:" + student2.getName() );
}
}
此时输出的结果为:
学生1的name:李四
学生2的name:李四
小结:此时student1和student2指向的是同一个内存地址,student2的值改变了,student1的值也随之改变。
原型模式实现对象复制
public class Student implements Cloneable{
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
protected Object clone(){
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student();
student1.setName("张三");
Student student2 = (Student) student1.clone();
student2.setName("李四");
System.out.println("学生1和学生2对象是否相等:" +(student1 == student2));
System.out.println("学生1的name:" + student1.getName());
System.out.println("学生2的name:" + student2.getName() );
}
}
此时输出的结果为:
学生1和学生2对象是否相等:false
学生1的name:张三
学生2的name:李四
小结:通过clone方法复制对象才是真正的对象复制,它是一个完全独立的对象 (不同的内存地址)
深拷贝和浅拷贝
浅拷贝:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向对象的内存地址;
深拷贝:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
浅拷贝的问题
public class Teacher implements Cloneable{
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
protected Object clone() {
Teacher teacher = null;
try {
teacher = (Teacher) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return teacher;
}
}
public class Student implements Cloneable{
private String name;
private Teacher teacher;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public Teacher getTeacher() {
return teacher;
}
@Override
protected Object clone(){
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student();
student1.setName("张三");
Teacher teacher1 = new Teacher();
teacher1.setName("张老师");
student1.setTeacher(teacher1);
Student student2 = (Student) student1.clone();
student2.setName("李四");
student2.getTeacher().setName("李老师");
System.out.println("学生" + student1.getName() +"的老师是:" + student1.getTeacher().getName());
System.out.println("学生" + student2.getName() +"的老师是:" + student2.getTeacher().getName());
}
}
输出结果:
学生张三的老师是:李老师
学生李四的老师是:李老师
小结:在给李四修改老师的时候,张三的老师也跟着被修改了
深拷贝
方式一:重写Student类的clone方法实现深拷贝
@Override
protected Object clone(){
Student stu = null;
try {
stu = (Student) super.clone();
Teacher teacher = (Teacher) stu.getTeacher().clone(); //克隆teacher对象
stu.setTeacher(teacher);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
此时输出结果为:
学生张三的老师是:张老师
学生李四的老师是:李老师
如果Student还有一个address属性,那么clone方法还需要加上克隆address的代码
/**
* 基于clone方法实现深拷贝
* @return
*/
@Override
protected Object clone(){
Student stu = null;
try {
stu = (Student) super.clone();
Teacher teacher = (Teacher) stu.getTeacher().clone(); //克隆teacher对象
stu.setTeacher(teacher);
Address address = (Address) stu.getAddress().clone(); //克隆address对象
stu.setAddress(address);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
方式二:使用序列化实现深拷贝(推荐)
/**
* 使用序列化实现深拷贝
* @return
*/
public Object deepCLone(){
Student stu = null;
try(
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
){
//序列化,当前对象以流的方式输出
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
//反序列化
stu = (Student) ois.readObject();
bis.close();
ois.close();
} catch (Exception e){
e.printStackTrace();
}
return stu;
}
此时输出结果为:
学生张三的老师是:张老师
学生李四的老师是:李老师