问题描述
现在有一只羊tom,姓名为tom,年龄为1,颜色为白色,请编写程序创建和tom属性完全相同的10只羊
传统思维
直接new出一个对象
优点:比较好理解,简单易操作
缺点:
- 在创建新的对象的时候,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率低。
- 总是需要重新初始化对象,而不是动态的对象运行时的状态,不够灵活
使用原型模式
概括
- 原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式。允许一个对象再创建另一个可制定对象,无需知道如何创建的细节
- 工作原理:通过一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实现创建,即 对象.new()
- 思路:利用Object超类的clone()方法,可以将对象复制一份,但是实现clone()的java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制能力。
- 缺点:如果需要对一个类配备一个克隆方法,这对全新的类不难,但对已有的类进行改造的时候,需要修改源代码,违背了OCP(开闭)原则。
- 优点:简化对象创建过程,同时也能够提高效率;不用重新初始化对象,而是动态获取对象运行时状态;但对象发生变化时,克隆对象也会发生变化,无需修改代码。
实现
创建可以clone的类,实现Cloneable接口,显示重写clone方法
public class Sleep implements Cloneable{
private String name;
private int age;
private String color;
//构造器
public Sleep(String name, int age, String color){
super();
this.name = name;
this.age = age;
this.color = color;
}
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 String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sleep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}
//显示实现clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Sleep sleep = null;
try {
sleep = (Sleep) super.clone();
}catch (Exception e){
e.printStackTrace();
}
return sleep;
}
}
实现克隆
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Sleep sleep = new Sleep("tom",1,"白色");
Sleep sleep1 = (Sleep) sleep.clone();
Sleep sleep2 = (Sleep) sleep.clone();
Sleep sleep3 = (Sleep) sleep.clone();
Sleep sleep4 = (Sleep) sleep.clone();
Sleep sleep5 = (Sleep) sleep.clone();
System.out.println(sleep1+ "" +sleep1.hashCode());
System.out.println(sleep2+ "" +sleep2.hashCode());
System.out.println(sleep3+ "" +sleep3.hashCode());
System.out.println(sleep4+ "" +sleep4.hashCode());
System.out.println(sleep5+ "" +sleep5.hashCode());
}
}
使用原型模式的时候如果原型发生变化,不需要改变其他的属性,简化代码。
原型模式在Spring框架中源码分析
- Spring中原型bean的创建,就是原型模式(多例)
<bean id = "id01" class="com.retemin.spring.bean.linsir" scope="prototype">
原型模型的深拷贝问题
当被拷贝的对象包含其他引用类型对象的时候,克隆的时候不会复制所包含的引用对象,而是将原对象中的引用对象的地址复制了一份,没有创建新的引用对象。这是一种浅拷贝。
代码示例,sleep中包含引用对象 friend
public Sleep friend;//引用对象
对sleep进行拷贝
public static void main(String[] args) throws CloneNotSupportedException {
Sleep sleep = new Sleep("tom",1,"白色");
sleep.friend = new Sleep("jack",1,"白色");
Sleep sleep1 = (Sleep) sleep.clone();
Sleep sleep2 = (Sleep) sleep.clone();
Sleep sleep3 = (Sleep) sleep.clone();
Sleep sleep4 = (Sleep) sleep.clone();
Sleep sleep5 = (Sleep) sleep.clone();
System.out.println(sleep1+ "" +sleep1.hashCode() +" "+sleep1.friend.hashCode());
System.out.println(sleep2+ "" +sleep2.hashCode() +" "+sleep2.friend.hashCode());
System.out.println(sleep3+ "" +sleep3.hashCode() +" "+sleep3.friend.hashCode());
System.out.println(sleep4+ "" +sleep4.hashCode()+" "+sleep4.friend.hashCode());
System.out.println(sleep5+ "" +sleep5.hashCode()+" "+sleep5.friend.hashCode());
}
得到的结果,所引用对象的hashcode是一样的
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}21685669 1735600054
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}2133927002 1735600054
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}1836019240 1735600054
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}325040804 1735600054
Sleep{name='tom', age=1, color='白色', fridend='1735600054'}1173230247 1735600054
结论
1、浅拷贝的介绍:对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将属性复制一份给新的对象
2、对于数据类型是引用类型的成员变量,比如说数据,某个类,浅拷贝会进行引用传递,也就是说只是内存地址的拷贝,不会拷贝实际的值,因此拷贝后的对象一旦发生改变,所拷贝的所有地方的引用对象都会发生改变
3、浅拷贝使用默认的clone()方法来实现sleep = (Sleep)super.clone()
深拷贝介绍
1、复制的对象是所有基本类型的成员变量值
2、为所有引用数据类型的成员变量申请存储空间,并复制每一个引用数据类型成员变量所引用的对象,深拷贝是对整个对象的拷贝,而不只是引用(内存地址)的拷贝
3、实现方式1:重写clone方法实现;2:通过对象序列化实现深拷贝
深拷贝案例
创建一个被引用对象
public class DeepCloneableTarget implements Serializable,Cloneable {
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
//构造器
public DeepCloneableTarget(String cloneName,String cloneClass){
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
//没有引用类型的成员变量,使用默认的clone进行浅拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
创建一个引用对象包含引用对象,通过两种方式进行深拷贝,重写clone方法和序列化
public class DeepPrototype implements Serializable,Cloneable {
public String name;
public DeepCloneableTarget deepCloneableTarget;
public DeepPrototype(){
super();
}
//深拷贝1,修改clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
deep = super.clone();
//对引用的类型进行单独处理
DeepPrototype deepPrototype = (DeepPrototype) deep;
deepPrototype.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();
return deepPrototype;
}
//深拷贝2,通过对象序列化实现(推荐)
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try{
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepPrototype deepPrototype = (DeepPrototype) ois.readObject();
return deepPrototype;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
finally {
try{
ois.close();
bis.close();
oos.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
测试
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
DeepPrototype deepPrototype = new DeepPrototype();
deepPrototype.name = "retmein";
deepPrototype.deepCloneableTarget = new DeepCloneableTarget("测试","类");
//方式1进行深拷贝
DeepPrototype deepPrototype1 = (DeepPrototype) deepPrototype.clone();
// DeepPrototype deepPrototype1 = (DeepPrototype)deepPrototype.deepClone();
//打印结果,如果引用对象的hashcode不一样,可以说明深拷贝完成
System.out.println("deepPrototype.name= "+ deepPrototype.name+" deepPrototype.deepCloneableTarget="+deepPrototype.deepCloneableTarget.hashCode());
System.out.println("deepPrototype1.name= "+ deepPrototype1.name+" deepPrototype.deepCloneableTarget="+deepPrototype1.deepCloneableTarget.hashCode());
}
}
两种方式的结果
deepPrototype.name= retmein deepPrototype.deepCloneableTarget=1735600054
deepPrototype1.name= retmein deepPrototype.deepCloneableTarget=21685669
可以看到DeepPrototype的引用对象变量的hashcode不同,可以知道深拷贝成功。
分析两种深拷贝方式
重写clone方法,需要重新创建一个所引用的对象,当修改的时候需要修改clone方法,计较麻烦。推荐使用序列化的方法。
来源:CSDN
作者:retemin
链接:https://blog.csdn.net/linsir20/article/details/104181356