目录
一、问题的提出
二、原型模式
三、原型模式具体实现方法
(1)利用构造函数方法
浅复制
深复制
(2)利用Cloneable接口方法
浅复制
深复制
(3)利用Serializable序列化接口方法
原型模式是指用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。适合原型模式的情景如下:
在计算机程序开发过程中,有时会遇到为一个类创建多个实例的情况,这些实例内部成员往往完全相同或有细微的差异,而且实力的创建开销比较大或者需要输入较多参数。如果能通过复制一个已创建的对象实例来重复创建多个相同的对象,这就可以大大减少创建对象的开销,这个时候就需要原型模式。
原型模式是指使用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,原型模式是一种对象创建型模式。
原型模式复制功能分为浅复制和深复制:
(1)浅复制
浅复制(Shallow Copy)是指在复制一个对象时,仅复制对象本身及其所有基本数据类型的成员变量。而对于非基本数据类型的成员变量,则只是将其引用复制过来,因此,原对象和复制后的对象会共享同一个引用对象。因此,如果原对象的引用对象发生改变,复制后的对象也会受到影响。
(2)深复制
深复制(Deep Copy)是指在复制一个对象时,不仅复制对象本身及其所有基本数据类型的成员变量,而且会对所有的非基本数据类型的成员变量进行递归复制。因此,原对象和复制后的对象不会共享任何引用对象。
student类:
student类包含两个基本数据类型name、age、一个引用类型变量add。
package shejimoshi.yuanxing;
public class Student {
String name;
int age;
Address add;
public Student(String name, int age, Address add) {
this.name = name;
this.age = age;
this.add = add; //籍贯
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAdd() {
return add;
}
public void setAdd(Address add) {
this.add = add;
}
}
Address类:
package shejimoshi.yuanxing;
class Address{
String pro; //出生省
String city; //出生市
String zip; //出生地邮编
public Address(String pro, String city, String zip) {
this.pro = pro;
this.city = city;
this.zip = zip;
}
public String getPro() {
return pro;
}
public void setPro(String pro) {
this.pro = pro;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
}
原型复制常用方法有三种:利用构造函数方法、利用Cloneable接口方法、利用Serializable序列化接口方法。
所需类代码如下所示:
这里写了一个构造方法,传入一个Student对象进行复制。利用已经创建好的学生对象s,构建复制对象copy_s。
package shejimoshi.yuanxing;
public class Student {
String name;
int age;
Address add;
public Student(String name, int age, Address add) {
this.name = name;
this.age = age;
this.add = add; //籍贯
}
Student(Student s) {
name = s.getName();
age = s.getAge();
add = s.getAdd(); //引用地址拷贝
}
}
测试类:
public class Test {
public static void main(String[] args) {
Address address = new Address("辽宁","大连","1618020202");
Student s = new Student("zhangyin",20,address);
//在这里复制出一个对象
Student copy_s = new Student(s);
System.out.println(copy_s.getName());
System.out.println(copy_s.getAge());
System.out.println(copy_s.getAdd().city);
}
}
在深复制中其实就是复制引用类型对象时创建了一个新的
public class Student {
String name;
int age;
Address add;
public Student(String name, int age, Address add) {
this.name = name;
this.age = age;
this.add = add; //籍贯
}
Student(Student s) {
name = s.getName();
age = s.getAge();
add = new Address(s.getAdd());
}
}
class Address{
String pro; //出生省
String city; //出生市
String zip; //出生地邮编
public Address(String pro, String city, String zip) {
this.pro = pro;
this.city = city;
this.zip = zip;
}
public Address(Address address) {
pro = address.getPro();
city = address.getCity();
zip = address.getZip();
}
//省略setter getter
}
Java类都继承自Object类。事实上,Object类提供了一个clone() 方法,可以将一个Java对象复制一份,因此在Java中可以直接用Object提供clone()方法来实现对象的克隆。但是clone是一个protected的方法,外部类不能直接调用。在此Java规定了对象复制规范:能够实现复制的Java类必须实现一个标识接口Cloneable。
该接口中没有定义任何方法,因此它仅是起到一个标识作用,表达的语义是:该类用到了对象复制功能,因此抛开本模式而言,空接口有时也是非常有意义的。
public class Student implements Cloneable{
String name;
int age;
Address add;
public Student(String name, int age, Address add) {
this.name = name;
this.age = age;
this.add = add; //籍贯
}
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
测试类:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("辽宁","大连","1618020202");
Student zy = new Student("zhangyin",20,address);
//深复制
Student copy_zy = (Student) zy.clone();
System.out.println(copy_zy.getName());
System.out.println(copy_zy.getAge());
System.out.println(copy_zy.getAdd().city);
}
}
public class Student implements Cloneable{
String name;
int age;
Address add;
public Student(String name, int age, Address add) {
this.name = name;
this.age = age;
this.add = add; //籍贯
}
protected Object clone() throws CloneNotSupportedException {
Student s = (Student)super.clone();
s.setAdd((Address) add.clone());
return s;
}
}
class Address implements Cloneable{
String pro; //出生省
String city; //出生市
String zip; //出生地邮编
public Address(String pro, String city, String zip) {
this.pro = pro;
this.city = city;
this.zip = zip;
}
protected Object clone() throws CloneNotSupportedException{
return (Address)super.clone();
}
}
利用构造方法、Cloneable接口方法实现对象深复制都稍显复杂,而利用Serializable序列化接口方法实现深复制要简单的多。Serializable接口同样是一个空接口,表示该对象支持序列化技术。
这是一个 Java 中的 clone() 方法的实现,它创建了一个与原始对象状态相同的新对象实例。该方法创建了一个新的 ByteArrayOutputStream 和 ObjectOutputStream 对象,用于将原始对象序列化为字节流。然后,使用 ByteArrayInputStream 和 ObjectInputStream 对字节流进行反序列化,并将结果对象作为克隆返回。
需要注意的是,该实现仅适用于被克隆的类可序列化,这意味着它实现了 java.io.Serializable 接口。如果类不可序列化,则会抛出异常。
此外,在序列化或反序列化过程中可能发生任何异常,需要进行异常处理。在此实现中,任何异常都会被捕获并使用 printStackTrace() 方法打印到标准错误输出。但这并不是一个好习惯,因为它会使错误诊断变得困难。更好的做法是抛出一个 CloneNotSupportedException 异常,并提供有意义的错误消息。
public class Student implements Cloneable, Serializable {
String name;
int age;
Address add;
public Student(String name, int age, Address add) {
this.name = name;
this.age = age;
this.add = add; //籍贯
}
protected Object clone() throws CloneNotSupportedException {
//在这一行,我们创建了一个 obj 对象,用于存储克隆后的对象。
Object obj = null;
try {
/*
* 这三行代码使用 Java 的序列化机制将当前对象 this 序列化为字节数组并存储在 bos 中。
* 首先,我们创建了一个 ByteArrayOutputStream 对象 bos,用于存储序列化后的字节数组。
* 接着,我们创建了一个 ObjectOutputStream 对象 oos,它的作用是将对象序列化为字节流。
* 最后,我们将当前对象 this 写入 oos 对象中,从而将其序列化为字节数组并存储在 bos 中。
* */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//从流里读回来
/*
* 这三行代码将 bos 中的字节数组反序列化为对象。
* 我们首先使用 ByteArrayInputStream 对象 bis 将字节数组包装为输入流。
* 接着,我们使用 ObjectInputStream 对象 ois,它的作用是将字节流反序列化为对象。
* 最后,我们使用 ois 对象的 readObject() 方法从输入流中读取对象并将其存储在 obj 中。
* */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
obj = ois.readObject();
}catch (Exception e) {
e.printStackTrace();
}
return obj;
}
class Address implements Serializable {
String pro; //出生省
String city; //出生市
String zip; //出生地邮编
public Address(String pro, String city, String zip) {
this.pro = pro;
this.city = city;
this.zip = zip;
}
}
Serializable接口是Java中的一个标记接口,它不包含任何方法或字段,仅仅是用于标识一个类可以被序列化。序列化是指将对象转换为可存储或传输的格式的过程,反序列化则是将这种格式的数据还原成原始的对象。
要利用Serializable接口进行序列化和反序列化,需要完成以下步骤:
让类实现Serializable接口 需要让需要序列化的类实现Serializable接口,这样就可以将其对象序列化为字节流并传输或存储。
创建ObjectOutputStream对象 在进行序列化时,需要将对象序列化为字节流并传输或存储,这需要使用ObjectOutputStream对象来实现。创建ObjectOutputStream对象时需要传入一个OutputStream对象,OutputStream对象可以是文件输出流或网络输出流。
调用ObjectOutputStream的writeObject方法 使用ObjectOutputStream对象的writeObject方法将需要序列化的对象写入到输出流中。
创建ObjectInputStream对象 在进行反序列化时,需要将字节流还原为原始对象,这需要使用ObjectInputStream对象来实现。创建ObjectInputStream对象时需要传入一个InputStream对象,InputStream对象可以是文件输入流或网络输入流。
调用ObjectInputStream的readObject方法 使用ObjectInputStream对象的readObject方法将从输入流中读取序列化的对象,并返回原始的对象。
下面是一个使用Serializable接口进行序列化和反序列化的示例代码:
import java.io.*;
public class SerializableExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建一个需要序列化的对象
Person person = new Person("Tom", 20);
// 创建ObjectOutputStream对象,将对象序列化到文件中
FileOutputStream fileOutputStream = new FileOutputStream("person.dat");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);
objectOutputStream.close();
// 创建ObjectInputStream对象,将文件中的字节流反序列化为对象
FileInputStream fileInputStream = new FileInputStream("person.dat");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Person person2 = (Person) objectInputStream.readObject();
objectInputStream.close();
// 输出反序列化后的对象信息
System.out.println(person2.getName()); // Tom
System.out.println(person2.getAge()); // 20
}
}
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}