原型模式
原型模式(Prototype Pattern)是一种对象创建型模式,用原型实例制定创建对象的种类,并且通过复制这些原型创建新的对象。
在Java中,用 克隆 实现了原型模式
浅克隆
- 被复制对象的常量和引用对象都与源对象相同
- 浅克隆仅复制当前对象,而不复制对象内的成员对象(即成员对象均指向源成员对象)
代码实现:
Teacher类:
@Data // lombok 自动生成get和set
@AllArgsConstructor// lombok 自动生成构造函数
public class Teacher implements Cloneable { // 实现了Cloneable接口 克隆时使用
public int age;
public String name;
public ArrayList<Student> students;
}
测试结果:
public static void main(String[] args) throws CloneNotSupportedException {
ArrayList<Student> students = new ArrayList<Student>();
students.add(new Student("jjj"));
Teacher teacher = new Teacher(11, "aaa",students);
Teacher teacher1 = (Teacher) teacher.clone();
System.out.println(teacher == teacher1);// false
System.out.println(teacher.students == teacher1.students);// true
}
解释:
- Teacher类实现了Cloneable接口,
- 则在进行克隆时生成的teacher1是一个新的对象,
- 但由于没有对类内部成员进行进一步克隆,所以teacher.students指向的是同一个变量
- 这就是浅克隆
解决内部成员对象的克隆问题,就成为了深克隆↓
深克隆
- 成员变量与成员对象的值与源克隆对象值相同
- 成员对象所指向的地址(成员对象)与源克隆对象不同
- 引用对象均与源对象内的成员不同,但值相同
使用Cloneable接口实现深克隆(推荐)
Cloneable接口
由于浅克隆没有对类内部成员对象进行克隆,则我们可以通过重写clone()
方法自定义我们需要克隆的成员对象
需要注意的是,克隆对象的clone()
方法并不属于Cloneable
接口,它是Object类
的一个方法。这一方法的定义如下:
protected native Object clone() throws CloneNotSupportedException;
可以看到他是一个protected
方法,即**Object的子类**才可以使用此方法。如果在没有重写Object的clone()方法且没有实现Cloneable接口的实例上调用clone方法,会报CloneNotSupportedException
异常。
为什么一定要实现一个空的Cloneable接口呢?
原因在于它相当于一个标志,没有实现该接口的类是无法调用clone()方法的。这里的标志判断是在native方法中进行。
我们在重写clone()方法后,需要将该方法设置为public。
重写clone方法
@Override
public Object clone() throws CloneNotSupportedException {
Teacher teacher = (Teacher) super.clone();// 调用super类克隆一个teacher实例 相对于 new Object
// teacher.age = this.age; // age为int类型,是常量不需新建对象
// teacher.name = this.name ;// name为string类型 也是常量不需新建 具体疑惑请看文章结尾
teacher.students = (ArrayList) this.students.clone();// students为引用类型,需要进一步克隆,使得源students与新students值相同,对象不同。完成深克隆
return teacher;// 返回克隆成功后的对象
}
解释:
- 成员变量为基本类型时(常量)不用进行二次克隆
- 成员变量为引用类型时(对象)需要进行二次克隆
- 成员变量为String类型时 此时String为变量,不需二次克隆 (具体看结尾处)
测试结果
public static void main(String[] args) throws CloneNotSupportedException {
ArrayList<Student> students = new ArrayList<Student>();
students.add(new Student("jjj"));
Teacher teacher = new Teacher(11, new StringBuffer("aaa"),students);
Teacher teacher1 = (Teacher) teacher.clone();
System.out.println(teacher == teacher1);// false
System.out.println(teacher.students == teacher1.students);// false
}
可见,两位老师的学生都不是同一个学生了,深克隆成功!
使用序列化与反序列化实现深克隆(存在线程安全问题)
序列化 - Serializable
定义:将实现了Serializable
接口(标记型接口)的对象转换成一个字节数组,并可以将该字节数组转为原来的对象。****
在此处我们运用Java序列化时能够保存对象信息,反序列化时会新建对象存储信息的机制,完成Java的深克隆
要使用序列化与反序列化,只需要实现 Serializable
接口即可;
具体实现
public Teacher deepClone() throws IOException, ClassNotFoundException {
// 将对象写入流中
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this); // 传入自身这个对象
//将对象从流中取出
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (Teacher) objectInputStream.readObject();
}
测试结果
结果与Clone的结果一致,在此就不进行展示了