原型模式定义: 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
优点;
缺点:
主要组成:
分类: 浅克隆 和 深克隆 两种
Java 中的 Object 类提供了浅克隆到 clone 方法,具体原型类只要实现 cloneable 接口就可以实现对象的浅克隆。
模板代码:
浅拷贝:直接用等号赋值
public class Demo{
public static void main(String[] args) {
Object a = new Object();
Object b = a; // 浅克隆
System.out.println(a == b); // 输出结果为 true
}
}
深拷贝:实现 Cloneable 接口,重写 clone 方法,对于非基本类型的属性需要重新new对象,
Java 自带的 clone 方法实际上是一种浅克隆,只会克隆外层的对象,其内部的对象还是需要手动 new 的
class User implements Cloneable{
String name;
public User(String name) {this.name = name;}
public String getName() {
return name;
}
@Override
protected User clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.name = new String(name);
return user;
}
}
public class Demo{
public static void main(String[] args) throws CloneNotSupportedException {
User uni1 = new User("uni");
User uni2 = uni1.clone();
System.out.println(uni1 == uni2); // false
System.out.println(uni1.getName() == uni2.getName()); // false
}
}
应用场景:
在 JDK 8 源码中, Clonable 是一个从JDK1.0 出现的没有任何声明方法的空接口,源码如下:
package java.lang;
public interface Cloneable {
}
Java 提供 Cloneable 接口的作用:在运行时通知 JVM 可以安全地在该类上使用 clone () 方法,而如果该类没有实现 Clonebale 接口,那么调用 clone 方法则会抛出 CloneNotSupportedException 异常
使用 clone 方法需满足的条件:
o.clone().equals(o) == true 恒成立基于之前的例子,我们定义了支持深克隆的 User对象,代码如下:
class User implements Cloneable{
String name;
public User(String name) {this.name = name;}
public String getName() {
return name;
}
@Override
protected User clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.name = new String(name);
return user;
}
}
其中 super.clone() 方法直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,因此效率很高,由于是基于内存复制的,故不会调用对象的构造方法,即不会经历初始化。
Java 自带的 clone 方法属于浅克隆,对于 User 对象里的引用类型 String ,在克隆时仍需要手动进行 new,而在 Java 中,要想真正实现深克隆,通常会使用 序列化(Serializable)的方式,即需要实现 Serializable 接口,比如:
import java.io.*;
class User implements Cloneable, Serializable {
String name;
public User(String name) {this.name = name;}
public String getName() {
return name;
}
@Override
protected User clone() throws CloneNotSupportedException {
try{
// 1. 创建字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 2. 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(bos);
// 3. 将该对象写入输出流中
oos.writeObject(this);
// 4. 创建字节数组输入流
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
// 5. 创建对象数组输入流
ObjectInputStream ois = new ObjectInputStream(bis);
// 6. 读取对象输入流中的对象
return (User) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
public class Demo{
public static void main(String[] args) throws CloneNotSupportedException {
User uni1 = new User("uni");
User uni2 = uni1.clone();
System.out.println(uni1 == uni2);
System.out.println(uni1.getName() == uni2.getName());
}
}
如果复制的目标对象正好是单例对象,那么是否会破坏单例对象?
例如:给单例模式的对象提供 clone 方法
class User implements Cloneable{
// 单例模式: 饿汉式
private static User instance = new User();
// 私有化无参构造
private User() {}
// 提供获取实例的方法
public static User getInstance() { return instance; }
// 克隆
@Override
protected User clone() {
try {
return (User) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
public class Demo{
public static void main(String[] args) {
// 创建原型对象
User user = User.getInstance();
// 复制原型对象
User cloneUser = user.clone();
System.out.println("原型对象比较克隆对象的结果为: " + (user == cloneUser));
}
}
运行结果
原型对象比较克隆对象的结果为: false
从运行结果看, clone 方法创建了两个不同的对象,这违背了单例模式的设计原则,为了防止破坏单例对象,可以采取的策略是 禁止复制,接下来有两种方式:
方式一:【推荐】不实现 Cloneable 接口,这样对象就无法克隆
方式二:实现 Cloneable 接口,重写 clone 方法,直接返回单例对象
protected User clone() {
return instance;
}
通过之前的学习回顾,原型模式分为浅克隆和深克隆,而深克隆通常要实现 Cloneable 接口,并重写 clone() 方法,所以我们只需在源码中查看哪些类实现了 Clonebale方法,如果实现了说明这个类很有可能就是支持深克隆的原型模式,接下来我们从 JDK 中运用比较多的 ArrayList 开始了解原型模式。
package java.util;
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public Object clone() {
try {
// 1. 直接克隆整个列表
ArrayList<?> v = (ArrayList<?>) super.clone();
// 2. 调用 Ararys.copyof 克隆列表的每个元素
v.elementData = Arrays.copyOf(elementData, size);
// 3. 初始化modCount, 表示列表在结构上被修改的次数
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
}
从 java.util.ArrayList源码中的 clone 方法可以看出
import java.util.ArrayList;
class User{
private String name;
public User(String name) {this.name = name; }
public void setName(String name) {this.name = name;}
@Override
public String toString() {
return "User{name='" + name + '\'' + '}';
}
}
public class Demo{
public static void main(String[] args) {
ArrayList<User> students = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
students.add(new User("user" + i));
}
ArrayList<User> cloneStudents = (ArrayList<User>) students.clone();
System.out.println(students);
System.out.println(cloneStudents);
System.out.println("克隆列表与原型列表对比: " + (students == cloneStudents));
for (int i = 0; i < students.size(); i++) {
System.out.println(
"克隆列表的元素与原型列表元素的对比: [" + i + "]" +
(students.get(i) == students.get(i)));
}
}
}
运行结果
[User{name=‘user1’}, User{name=‘user2’}, User{name=‘user3’}]
[User{name=‘user1’}, User{name=‘user2’}, User{name=‘user3’}]
克隆列表与原型列表对比: false
克隆列表的元素与原型列表元素的对比: [0]true
克隆列表的元素与原型列表元素的对比: [1]true
克隆列表的元素与原型列表元素的对比: [2]true
这里可以看到,因为 ArrayList 存储的对象 User 没有实现 Cloneable 接口,调用 ArrayList 的 clone() 方法,最后输出的结果虽然是不同的 ArrayList 对象,但是其内部的 User 对象仍然是相等的,即没有实现真正的深克隆。
接下来,修改克隆后列表里的第一项,然后输出原型列表
public class Demo{
public static void main(String[] args) {
ArrayList<User> students = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
students.add(new User("user" + i));
}
ArrayList<User> cloneStudents = (ArrayList<User>) students.clone(); // 克隆列表
User user = cloneStudents.get(0); // 获取克隆列表里的用户对象
user.setName("update user."); // 修改用户名称
students.forEach(System.out::println); // 输出原型用户列表
}
}
运行结果
User{name=‘update user.’}
User{name=‘user2’}
User{name=‘user3’}
结果显示,原型列表里的User对象被修改了,说明之前就是浅拷贝到形式,虽然两个列表指向的地址不同,但是其内部的 User 对象所指向的地址仍然是同一个。
最简单的解决办法就是提供一个基于字节流的深克隆方法:
/*
List 内所有元素必须实现 Serializable 接口,否则在序列化时候会被报错
*/
public static List clone(List src){
try{
// 拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(src);
// 保存
ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bai);
return (List) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
// 不处理
}
return null;
}
原型模式在源码中应用广泛,实现了 Cloneable 接口的类一般是与克隆相关的,原型模式中的原型是指克隆时参考的对象,克隆分为浅克隆和深克隆,Object类提供的clone 方法属于浅克隆,实现深克隆的最简单方式就是基于字节流进行操作,一共分为六步,进行深克隆需保证里面的非基本类型都实现了 Serializable 接口,即支持可序列化:
// 1. 创建字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 2. 转化为对象输出流
ObjectOutputStream oos = new ObjectOutputStream(bos);
// 3. 将深克隆的对象写入到对象输出流
oos.writeObject(src);
// 4. 将 字节数组输出流 读取为 字节数组输入流
ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());
// 5. 转化为对象输入流
ObjectInputStream ois = new ObjectInputStream(bai);
// 6. 读取对象
return (List) ois.readObject();