原型模式的介绍
原型模式我们也称为克隆模式,即根据某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。原型模式的克隆方式有两种:浅克隆和深度克隆:
-
浅克隆:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,(浅克隆)浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。 Object类提供的方法clone只是拷贝本对象 , 其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址。看下图:v1(原型对象),v2(v2是由v1克隆出来的)里面的一个引用类型的变量是指向的同一块内存地址,要修改这个变量的话,v1,v2都会被改动
-
深克隆:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。看下图:通过v1克隆v2时,把里面的变量属性也克隆了一份,并分配了内存空间。
运用场景
如果需要短时间创建大量对象的情况下,原型模式就是我们可以考虑实现的方式。
因为在Java中通过new关键字创建的对象是非常繁琐的(类加载判断,内存分配,初始化等),这就意味着这个过程是很耗费资源与时间的。下面也有两者的对比。
原型模式之浅克隆
package com.wlw.prototype.qianCopydemo01;
import java.util.Date;
/*
原型模式的实现,就是依靠克隆,先创建出一个对象v1,通过v1 克隆出 v2
实现克隆:
1.实现接口Cloneable
2.重写clone()方法
*/
//视频类
public class Video implements Cloneable {
private String name;
private Date date;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Video() {
}
public Video(String name, Date date) {
this.name = name;
this.date = date;
}
//get set toString 省略.....
}
package com.wlw.prototype.qianCopydemo01;
import java.util.Date;
public class BiliBili {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
//创建原型对象v1
Video v1 = new Video("java",date);
System.out.println("v1=>"+v1);
System.out.println("v1.hashcode=>"+v1.hashCode());
//通过v1 克隆出 v2 (v2和v1是完全一样的,除了hashCode,因为v2也是一个新的对象)
Video v2 = (Video)v1.clone();
System.out.println("v2=>"+v2);
System.out.println("v2.hashcode=>"+v2.hashCode());
}
}
/*
v1=>Video{name='java', date=Tue Oct 20 15:41:25 CST 2020}
v1.hashcode=>21685669
v2=>Video{name='java', date=Tue Oct 20 15:41:25 CST 2020}
v2.hashcode=>2133927002
*/
- 看一下浅克隆的问题:虽然产生了两个完全不同的对象,但是被复制的对象里的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
- 看下面的例子,v2是由v1复制出来的,而Video类里有个Date类型的变量,此时v1与v2这两个对象里的Date类型的变量是同一个,改变一个,另一个也会跟着改变。
package com.wlw.prototype.qianCopydemo01;
import java.util.Date;
public class BiliBili {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
//创建原型对象v1
Video v1 = new Video("java",date);
//通过v1 克隆出 v2 (v2和v1是完全一样的,除了hashCode,因为v2也是一个新的对象)
Video v2 = (Video)v1.clone();
System.out.println("v1=>"+v1);
System.out.println("v2=>"+v2);
System.out.println("=========================");
date.setTime(22135321);
System.out.println("v1=>"+v1);
System.out.println("v2=>"+v2);
}
}
/*
v1=>Video{name='java', date=Tue Oct 20 15:45:59 CST 2020}
v2=>Video{name='java', date=Tue Oct 20 15:45:59 CST 2020}
=========================
v1=>Video{name='java', date=Thu Jan 01 14:08:55 CST 1970}
v2=>Video{name='java', date=Thu Jan 01 14:08:55 CST 1970}
date更新之后,v1,v2里面的date全都更新了,这就是v1,v2指向了同一个date
*/
原型模式之深克隆
实现深克隆,有三种途径:
- 让每个引用类型属性内部都重写clone() 方法
- 使用序列化流
- 使用开源工具类(本质上和第二种是一样的,都是通过序列化)
这里实现深克隆使用的方式是:让每个引用类型属性内部都重写clone()方法。
另外两种可参考:java中赋值语句(=)与浅拷贝与深拷贝弄出来的对象有什么区别
package com.wlw.prototype.shenCopydemo02;
import java.util.Date;
/*
原型模式的实现,就是依靠克隆,先创建出一个对象v1,通过v1 克隆出 v2
实现浅克隆:
1.实现接口Cloneable
2.重写clone()方法
而要实现深克隆,有三种方式:
1.让每个引用类型属性内部都重写clone() 方法
2.使用序列化流
3.使用开源工具类
这里使用的是第一种方式:让每个引用类型属性内部都重写clone()方法
*/
//视频类
public class Video implements Cloneable {
private String name;
private Date date;
//深克隆
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
//深克隆
Video v = (Video) obj;
v.date = (Date)this.getDate().clone();//把对象的属性也克隆一份
return obj;
}
public Video() {
}
public Video(String name, Date date) {
this.name = name;
this.date = date;
}
//get set toString 省略.....
}
package com.wlw.prototype.shenCopydemo02;
import java.util.Date;
public class BiliBili {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
//创建原型对象v1
Video v1 = new Video("java",date);
//通过v1 克隆出 v2 (v2和v1是完全一样的,除了hashCode,因为v2也是一个新的对象)
Video v2 = (Video)v1.clone();
System.out.println("v1=>"+v1);
System.out.println("v2=>"+v2);
System.out.println("=========================");
date.setTime(22135321);
System.out.println("v1=>"+v1);
System.out.println("v2=>"+v2);
}
}
/*
v1=>Video{name='java', date=Tue Oct 20 18:18:59 CST 2020}
v2=>Video{name='java', date=Tue Oct 20 18:18:59 CST 2020}
=========================
v1=>Video{name='java', date=Thu Jan 01 14:08:55 CST 1970}
v2=>Video{name='java', date=Tue Oct 20 18:18:59 CST 2020}
只有v1的时间改变了,v2的时间并没有改变
*/
原型模式和直接new对象方式的比较
当我们需要大量的同一类型对象的时候可以使用原型模式,下面是两种方式的性能对比:
用两种方式同时生成10个对象:
/**
* 测试普通new方式创建对象和clone方式创建对象的效率差异!
* 如果需要短时间创建大量对象,并且new的过程比较耗时。则可以考虑使用原型模式!
*/
public class Test {
public static void testNew(int size){
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
User t = new User();
}
long end = System.currentTimeMillis();
System.out.println("new的方式创建耗时:"+(end-start));
}
public static void testClone(int size) throws CloneNotSupportedException{
long start = System.currentTimeMillis();
User t = new User();
for(int i=0;i<size;i++){
User temp = (User) t.clone();
}
long end = System.currentTimeMillis();
System.out.println("clone的方式创建耗时:"+(end-start));
}
public static void main(String[] args) throws Exception {
testNew(10);
testClone(10);
}
}
//用户类
class User implements Cloneable {
public User() {
try {
//模拟创建对象耗时的过程!
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
// 直接调用object对象的clone()方法!
Object obj = super.clone();
return obj;
}
}
/*
输出结果:
new的方式创建耗时:108
clone的方式创建耗时:11
*/
用两种方式同时生成1000个对象:
/**
* 测试普通new方式创建对象和clone方式创建对象的效率差异!
* 如果需要短时间创建大量对象,并且new的过程比较耗时。则可以考虑使用原型模式!
*/
public class Test {
public static void testNew(int size){
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
User t = new User();
}
long end = System.currentTimeMillis();
System.out.println("new的方式创建耗时:"+(end-start));
}
public static void testClone(int size) throws CloneNotSupportedException{
long start = System.currentTimeMillis();
User t = new User();
for(int i=0;i<size;i++){
User temp = (User) t.clone();
}
long end = System.currentTimeMillis();
System.out.println("clone的方式创建耗时:"+(end-start));
}
public static void main(String[] args) throws Exception {
testNew(1000);
testClone(1000);
}
}
/*
new的方式创建耗时:10836
clone的方式创建耗时:11
*/
小结:在短时间创建大量对象的情况下, 通过clone的方式性能开销基本没有什么影响,而new的方式随着实例的对象越来越多,性能会急剧下降。
开发中的应用场景
原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
在spring中bean的创建实际就是两种:单例模式和原型模式。在此看出原型模式一般是和工厂模式搭配起来一起使用。