课程名称:Java设计模式精讲 Debug方式+内存分析
课程章节:第9章 原型模式讲解+Coding+源码解析
主讲老师:Geely
课程内容:
问题1)定义
指原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
不需要知道任何创建的细节,不用调用构造函数。
问题2)使用场景?
1、初始化消耗较多资源。
2,New 一个对象需要非常繁琐的过程(数据准备,访问权限等)
3,、构造函数比较复杂
4、循环体中产生大量对象。
问题3)优点和缺点?
优点:原型模式创建对象比直接new一个对象性能高。
简化创建过程
缺点:
1、必须配备克隆方法。(继承克隆接口(只是一个标识,它是可以备克隆的),重写object的克隆方法。)
-
对克隆复杂对象或克隆出的对象进行复制改造时,容易引入风险。
-
深拷贝、浅拷贝要运用得当。
问题4)克隆原型是实现方法?
第一步:实现Cloneable接口。(标识该类是可以被克隆的)
第二步:重写object类的clone方法。(clone方法默认是浅克隆)
第三步:深刻隆还是浅克隆,直接在clone方法中添加属性就可以。
public class pig implements Cloneable{
private String name;
private Date brithday;
public pig(String name,Date brithday){
this.name = name;
this.brithday = brithday;
}
public Date getBrithday() {
return brithday;
}
public void setBrithday(Date brithday) {
this.brithday = brithday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "pig{" +
"name='" + name + '\'' +
", brithday=" + brithday +
'}'+super.toString();
}
@Override
protected Object clone() throws CloneNotSupportedException {
// 默认的浅克隆
// return super.clone();
//深克隆,就是将属性单独克隆。
pig pp = (pig)super.clone();
pp.brithday = (Date)pp.brithday.clone();
return pp;
}
}
public class test {
public static void main(String[] args) throws CloneNotSupportedException {
Date da = new Date(0L);
pig p1 = new pig("佩奇",da);
pig p2 = (pig) p1.clone();
System._out_.println("111111"+p1);
System._out_.println("222222"+p2);
p1.setName("小猪订单");
p1.setBrithday(new Date());
System._out_.println("111111"+p1);
System._out_.println("222222"+p2);
}
}
问题5)浅克隆和深克隆?
-
无论深克隆还是浅克隆,克隆出来的对象是和原来对象不是同一个地址。
-
浅克隆:共享原对象的非基本类型成员变量。(修改非基本成员变量,都会改变。)(基本成员变量不共享),浅克隆只是克隆基本类型成员变量,非基本类型成员变量是共享的。
-
深克隆:需要在clone方法中写上不同享的非基本成员变量。深克隆就是将非基本成员变量单独克隆。
1、浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
2、深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
(如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。)
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
扩展
Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,
这种空接口也称为标识接口,标识接口中没有任何方法的定义,
其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
3,解决多层克隆问题:先序列化对象,然后再
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
public class Outer implements Serializable {
private static final long _serialVersionUID_ = 369285298572941L;//最好是显式声明ID
public Inner inner;
//Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]
public Outer myclone() {
Outer outer = null;
try {
// 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
// 所以利用这个特性可以实现对象的深拷贝
**ByteArrayOutputStream baos =** **new** **ByteArrayOutputStream()****;**
**ObjectOutputStream oos =** **new** **ObjectOutputStream(baos)****;**
**oos.writeObject(****this****)****;**
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}
总结:
实现对象克隆有两种方式:
1).实现Cloneable接口并重写Object类中的clone()方法;
2).实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的
对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object
类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。
问题6)如何防止克隆破坏单例模式?
方法1:防止克隆破坏单例,不实现Cloneable克隆接口。
方法2:防止克隆破坏单例模式。
即使实现了Cloneable克隆接口,可以在clone方法中。直接返回创建好的实例。而不是返回super.clone。
测试:通过反射,修改clone为public方法,然后再通过clone方法创建对象。