原型模式-Prototype Pattern
可以通过一个原型对象克隆出多个一模一样的对象,该模式称之为原型模式。
浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
实现浅克隆的对象需要实现Cloneable接口
对象需要重写Object中的clone()方法,将方法权限设置为Public即可,然后方法体内调用Object的clone()方法。
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
实现深克隆的对象需要实现Serializable接口
public Object deepClone() throws Exception {
//将对象写入流中
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return ois.readObject();
}
由于以上克隆操作都需要在对象中添加对应方法,可以考虑提取一个工具类来简化操作。
下面附上浅克隆和深克隆的工具类代码
import java.io.*;
import java.lang.reflect.Field;
/**
* @author wenbin
* @date 2022/3/14 11:42
*/
public class CloneUtil {
/**
* <p>浅克隆 将传入的对象克隆一份返还</p>
* 泛型参数只能接受实现Cloneable接口的类
*
* @param obj 要克隆的对象
* @param <T> 该对象类型
* @return 克隆出的对象
*/
public static <T extends Cloneable> T shallowCloneObject(T obj) {
//获取要克隆的对象的Class对象
Class<? extends Cloneable> clazz = obj.getClass();
//拿到所有属性字段
Field[] fields = clazz.getDeclaredFields();
//声明出目标对象
T cloneObj = null;
try {
//实例化一个目标对象
cloneObj = (T) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
try {
//遍历传入的对象的所有属性的数组
for (Field field : fields) {
//设置该属性可见
field.setAccessible(true);
//拿到该属性的名称
String name = field.getName();
//拿到该属性的值
Object o = field.get(obj);
//赋值给目标对象
field.set(cloneObj, o);
}
} catch (Exception e) {
e.printStackTrace();
}
//返回目标对象
return cloneObj;
}
/**
* <p>深克隆 将传入的对象克隆一份返还</p>
* 泛型参数只能接受实现Serializable接口的类
*
* @param obj 要克隆的对象
* @param <T> 该对象类型
* @return 克隆出的对象
*/
public static <T extends Serializable> T deepCloneObject(T obj) {
//声明出目标对象
T cloneObj = null;
//在try,catch块外声明出输出输入流对象,用于finally里确保流的关闭
ObjectOutputStream obs = null;
ObjectInputStream ois = null;
try {
//创建字节数组输出流
ByteArrayOutputStream out = new ByteArrayOutputStream();
//将字节数组输出流放入对象输出流中
obs = new ObjectOutputStream(out);
//将传入的对象写到流中
obs.writeObject(obj);
//创建字节数组输入流,并把刚才流中的对象放入进来
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
//将字节数组输入流放入对象输入流中
ois = new ObjectInputStream(ios);
//读取流中的数据输入进来并赋值给目标对象,至此,克隆结束
cloneObj = (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//这里进行判空关闭 关闭外层流 里面包裹的流也会被自动关闭
if (obs != null) {
obs.close();
}
if (ois != null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//返回目标对象
return cloneObj;
}
}