原型模式:用一个已经创建的实例作为原型,将对象作为一个原型来创建一个和新的一模一样的对象。在这里,原型实例指定了要创建的对象。用这种方式创建对象较高效,且无须知道对象创建的细节。
Java提供了对象的clone()
方法,使得原型模式的实现非常简单。原型模式的实现类型有:浅拷贝和深拷贝。
1、浅拷贝
对象的成员变量类型可以是基本数据类型,也可以是引用类型;进行对象拷贝时,对基本数据类型的成员变量的值拷贝一份,赋给新对象,而对于引用类型,是将引用的内存地址值拷贝一份赋给新对象,这就是浅拷贝。
实现方式
- 原型对象的类实现JDK提供的接口
Cloneable
,并重写clone()
方法; - 浅拷贝比较简单,通过调用父类(即Object)的
clone()
方法实现拷贝; - 拷贝时,调用
clone()
方法即可拷贝新对象;
//(1)实现Cloneable
public class Teacher implements Cloneable{
private String classname;
private Student student;
//(2)重写clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//省略get和set方法...
//省略toString方法...
}
public class Student {
private String name;
private Integer age;
//省略get和set方法...
//省略toString方法...
}
测试类
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student("小明", 12);
Teacher teacher = new Teacher("六年级", student);
//(3)调用clone()实现拷贝
Teacher clone = (Teacher) teacher.clone();
System.out.println(teacher);
System.out.println(clone);
System.out.println("teacher.student=="+teacher.getStudent().hashCode());
System.out.println("clone.student=="+clone.getStudent().hashCode());
}
}
可以发现,浅拷贝后,两个对象是一模一样的,包括引用类型对象的引用值也是相同的。
浅拷贝后,原型对象和拷贝对象,其引用类型域共用相同的引用对象,如果该引用对象发生修改,则会同时影响原型对象和拷贝对象的状态,这并不是我们所期望的,使用原型,就是希望拷贝相同的对象,分别使用。所以,需要使用到深拷贝。
2、深拷贝
所谓深拷贝,在浅拷贝的基础上,除了基本类型的域,,原型对象的引用类型域的对象也会拷贝一份,再赋给拷贝对象。注意:拷贝引用类型域的变量,不是拷贝其引用的内存地址值,而是在内存中创建一个新的一模一样的对象后,再将其地址值赋给拷贝对象。
深拷贝避免了原型对象和拷贝对象的引用类型域引用相同对象的问题。但带来的是拷贝效率较低且花销更大的问题。
实现深拷贝的方式有两种:
Clone()
方法;- 对象序列化;
2.1 Clone()
方法
实现深拷贝,就是让原型对象的引用类型域的类同样实现Cloneable
接口,重写clone()
方法;
- 原型对象类实现接口
Cloneable
- 域的类同样实现接口
Cloneable
,并重写clone()
方法; - 重写原型对象的
clone()
方法,在方法内调用域对象的clone()
方法;
//(1)实现Cloneable
public class Teacher implements Cloneable{
private String classname;
private Student student; //引用类型,其类重写clone()方法
//(3)重写clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
//1.拷贝对象
Teacher clone = (Teacher) super.clone();
//2.拷贝成员变量的值
Student stuClone = (Student) student.clone();
//3.将成员变量的新值赋予新的对象
clone.setStudent(stuClone);
return clone;
}
//省略get和set方法...
//省略toString方法...
}
//(2)实现Cloneable
public class Student implements Cloneable {
private String name;
private Integer age;
//重写clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//省略get和set方法...
//省略toString方法...
}
经过测试(测试类与浅拷贝的一样),发现拷贝前后对象是一模一样,但引用类型的引用值不相同了,说明深拷贝成功。
2.2 序列化方式
使用序列化方式实现深拷贝的步骤:
- 所有类都需要实现
Serializable
接口; - 编写序列化对象的方法;
public class CloneMethod {
public static <T> T deepClone(T t) {
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//将原型对象写入流
oos.writeObject(t);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
//在从流中读取
return (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bos != null) {
bos.close();
}
if (oos != null) {
oos.close();
}
if (bis != null) {
bis.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
3、总结
- 使用原型模式可以简化对象的创建过程,比创建新的对象效率较高;
- 原型模式不用重新初始化对象,而是动态地获得对象运行时状态;
- 如果原始对象发生变化后,克隆的对象也存在这些变化,无需修改代码;