原型模式
介绍
1、用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
2、允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
3、工作原理
(1)Java 中 Object 类是所有类的根类,Object 类提供了一个 clone 方法,该方法可以将一个对象复制一份,但是需要实现 clone 的类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力
(2)通过将一个原型对象传给要发起创建的对象,该发起创建的对象通过请求原型对象拷贝它们自己来实施创建,即,对象.clone()
4、应用场景:
(1)创建新的对象比较复杂时,资源优化场景
(2)类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
(3)性能和安全要求的场景
(4)通过 new 产生一个对象需要非常繁琐的数据准备或访问权限
(5)一个对象多个修改者的场景,一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用
(6)原型模式一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者
5、优点
(1)不用重新初始化对象,而是动态地获得对象运行时的状态
(2)如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
(3)利用原型模式简化对象的创建过程,同时提高效率
6、缺点
(1)每一个类都要一个克隆方法,对已有的类进行改造时,需要修改其源代码,违背了开闭原则
(2)配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候
(3)必须实现 Cloneable 接口
7、控制可克隆性:将类定义为 final 是唯一保证可以阻止克隆的方法
8、最方便地克隆
(1)实现 Cloneable 接口
(2)重写 clone()
(3)在 clone() 中调用 super.clone()
(4)在 clone() 中捕捉 CloneNotSupportedException
9、相较于自行编写支持克隆的代码,更应该考虑使用 Apache 通用序列化实用工具类(Apache Commons Serialization Utility Classes),或深克隆库(deep cloning library),或者一些其他的克隆库
事项
1.final 修饰的 String 类,为不可变类型
(1)调用 Object 中的 clone 时,即浅拷贝引用数据类型,原数据、副本是指向常量池同一个 String
(2)在改变原数据或副本的 String 时,两者的 String 是不会相互影响的
(3)同时修改原数据和副本,使两者的 String 相同时,两者所指向常量池的 String 是同一个
2.Object 类中的 clone 方法是返回 Object 类型
(1)重写 clone 时,修改方法的返回类型,并在方法内强制转换类型
或
(2)在接受时,将 Object 强制转换类型
浅拷贝
1.浅拷贝是使用默认的 clone 来实现,即父类 Object 中的 clone 方法
2.不同成员变量类型的区别
(1)基本数据类型:直接进行值传递,即复制对象的所有基本数据类型的成员变量值,传给将新的对象
(2)引用数据类型:如成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象,在这种情况下,实际上两个对象的该成员变量都指向同一个实例,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
代码实现
public class ShallowCopy {//浅拷贝
public static void main(String[] args) throws CloneNotSupportedException {
Source source = new Source(1, "one");
Source clone = (Source) source.clone();
System.out.println("修改前");
System.out.println("原数据:" + source.no + " " + source.name.hashCode());
System.out.println("副本:" + clone.no + " " + clone.name.hashCode());
System.out.println("修改副本的基本数据类型");
clone.no = 2;
System.out.println("原数据:" + source.no);
System.out.println("副本:" + clone.no);
System.out.println("修改原数据、副本,使两者String相同");
source.name = "two";
clone.name = "two";
System.out.println("原数据:" + source.name.hashCode());
System.out.println("副本:" + clone.name.hashCode());
System.out.println("单独修改原数据的String");
source.name = "three";
System.out.println("原数据:" + source.name.hashCode());
System.out.println("副本:" + clone.name.hashCode());
}
}
class Source implements Cloneable {
public int no;
public String name;
public Source(int no, String name) {
this.no = no;
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
深拷贝
1、不同成员变量类型的区别
(1)基本数据类型:直接进行值传递,即复制对象的所有基本数据类型的成员变量值,传给将新的对象
(2)引用数据类型:为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,即对整个对象进行拷贝
2、实现方式
(1)重写 clone 方法
(2)通过对象序列化
3、序列化至少比 clone 慢一个量级
代码实现
import java.io.*;
public class DeepCopy {//深拷贝
public static void main(String[] args) throws CloneNotSupportedException {
Source source = new Source();
Quote quote = new Quote();
quote.name = "quote";
source.name = "source";
source.quote = quote;
System.out.println("方式1:重写clone");
Source clone1 = source.clone();
System.out.println("原数据:" + source.name + " " + source.quote.hashCode());
System.out.println("副本1:" + clone1.name + " " + clone1.quote.hashCode());
System.out.println("方式2:序列化对象");
Source clone2 = source.deepCopy();
System.out.println("原数据:" + source.name + " " + source.quote.hashCode());
System.out.println("副本2:" + clone2.name + " " + clone2.quote.hashCode());
}
}
class Source implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;//序列化版本号,提高兼容性
public String name;
public Quote quote;
//重写clone
@Override
protected Source clone() throws CloneNotSupportedException {
Source clone;
//使用Object类中的clone完成基本数据类型和String的克隆
clone = (Source) super.clone();
//对每个引用数据都要进行逐一处理,即浅拷贝
//本质:对原数据浅拷贝,再对引用数据浅拷贝
//若要进行彻底的深拷贝,需对Quote深拷贝,即以Source的方式,重写clone,直到最深层的类没有引用数据类型
clone.quote = quote.clone();
return clone;
}
//通过对象的序列化实现
//本质:利用IO流进行复制
public Source deepCopy() {
ByteArrayOutputStream bos;
ObjectOutputStream oos = null;
ByteArrayInputStream bis;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//当前对象以对象流的方式输出
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
//当前对象以对象流的方式输入
return (Source) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (oos != null) {
try {
oos.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
}
class Quote implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;//序列化版本号,提高兼容性
public String name;
@Override
protected Quote clone() throws CloneNotSupportedException {
return (Quote) super.clone();
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战