深拷贝与浅拷贝
当系统中需要大量创建相同或者相似的对象时,就可以通过“原型设计模式”来实现。原型模式是属于创建型模式。
核心思想:通过拷贝指定的"原型实例"来创建跟对象一样的新对象,一句话就是:克隆指定对象。
浅拷贝与深拷贝:
浅拷贝:拷贝一个对象时,只复制对象内部的基本数据类型,对象内部指向引用类型或者数组不复制。(其实也复制了,但是复制的是一个指针,新拷贝的对象和原型对象指向的是同一个实例)
深拷贝:拷贝一个对象时,对象内部的无论基本类型还是引用类型还是数组都复制。
//TODO 浅拷贝和深拷贝的原理图
浅拷贝
深拷贝
1.通过Cloneable
把要克隆的类实现Cloneable接口,并且实现Cloneable中的clone方法。
浅拷贝
Dog类
package com.jc.prototype;
import java.io.Serializable;
/**
* @program: 设计模式
*
* @description:
*
* @author: Mr.Wang
*
* @create: 2021-06-13 19:34
**/
public class Dog {
private int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Sheep类
package com.jc.prototype;
/**
* @program: 设计模式
* @description:
* @author: Mr.Wang
* @create: 2021-06-13 18:12
**/
public class Sheep implements Cloneable {
private int age;
private char sex;
private Dog friend;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public Dog getFriend() {
return friend;
}
public void setFriend(Dog friend) {
this.friend = friend;
}
public Sheep(int age, char sex, Dog friend) {
this.age = age;
this.sex = sex;
this.friend = friend;
}
public Sheep(){
}
@Override
protected Sheep clone() {
Sheep clone = null;
try {
//这里完成基本类型克隆
clone = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
@Override
public String toString() {
return "Sheep{" +
"age=" + age +
", sex=" + sex +
", friend=" + friend +
'}';
}
}
Client类
package com.jc.prototype;
import java.util.function.DoubleFunction;
/**
* @program: 设计模式
* @description:
* @author: Mr.Wang
* @create: 2021-06-13 18:21
**/
public class Client {
public static void main(String[] args) {
Dog friend = new Dog(3);
Sheep sheep = new Sheep(22,'男',friend);
Sheep clone = sheep.clone();
System.out.println("sheep' hashcode:"+sheep.hashCode()+", "+sheep+", friend' hashcode:"+sheep.getFriend().hashCode());
System.out.println("clone' hashcode:"+clone.hashCode()+", "+clone+", friend' hashcode:"+clone.getFriend().hashCode());
/**
* 打印结果
*sheep' hashcode:460141958, Sheep{age=22, sex=男, friend=Dog{age=3}}, friend' hashcode:1163157884
*clone' hashcode:1956725890, Sheep{age=22, sex=男, friend=Dog{age=3}}, friend' hashcode:1163157884
*/
}
}
通过打印结果可以验证这是浅拷贝,浅拷贝的不好就是,如果对引用类型数据进行修改的话,clone后的数据会跟着修改(看前边的原理图)。
例如我们的这个代码,克隆后重新设置下年龄。
Dog friend = new Dog(3);
Sheep sheep = new Sheep(22,'男',friend);
Sheep clone = sheep.clone();
//拷贝完了 如果我们在对Dog进行修改的话 clone后的对象还是会跟着更改,因为他们指向的都是friend实例
friend.setAge(5);
打印结果为
sheep’ hashcode:460141958, Sheep{age=22, sex=男, friend=Dog{age=5}}, friend’ hashcode:1163157884
clone’ hashcode:1956725890, Sheep{age=22, sex=男, friend=Dog{age=5}}, friend’ hashcode:1163157884
那么我们如何通过Cloneable深拷贝呢?
解决办法:让引用类型也实现clone接口,在Sheep的clone方法中分两步:
①对基本类型拷贝②对引用类型的真正复制.(新new一个)
深拷贝
1.让Dog类也实现Clone接口
package com.jc.prototype;
import java.io.Serializable;
/**
* @program: 设计模式
*
* @description:
*
* @author: Mr.Wang
*
* @create: 2021-06-13 19:34
**/
public class Dog implements Cloneable {
private int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2.Sheep中的clone方法改为这样
@Override
protected Sheep clone() {
Sheep clone = null;
try {
//1.这里完成基本类型克隆
clone = (Sheep) super.clone();
//2.完成引用类型的真正克隆
clone.friend = (Dog) friend.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
Client类
package com.jc.prototype;
import java.util.function.DoubleFunction;
/**
* @program: 设计模式
* @description:
* @author: Mr.Wang
* @create: 2021-06-13 18:21
**/
public class Client {
public static void main(String[] args) {
Dog friend = new Dog(3);
Sheep sheep = new Sheep(22,'男',friend);
Sheep clone = sheep.clone();
//拷贝完了 如果我们在对Dog进行修改的话 clone后的对象还是会跟着更改,因为他们指向的都是friend实例
friend.setAge(5);
System.out.println("sheep' hashcode:"+sheep.hashCode()+", "+sheep+", friend' hashcode:"+sheep.getFriend().hashCode());
System.out.println("clone' hashcode:"+clone.hashCode()+", "+clone+", friend' hashcode:"+clone.getFriend().hashCode());
/**
* 打印结果
*sheep' hashcode:460141958, Sheep{age=22, sex=男, friend=Dog{age=3}}, friend' hashcode:1163157884
*clone' hashcode:1956725890, Sheep{age=22, sex=男, friend=Dog{age=3}}, friend' hashcode:356573597
*/
}
}
通过打印结果可以看到Sheep实例和clone实例hashcode不同,friend的hashcode也不同,就算克隆后对friend进行修改,也不会影响clone的实例,这才是真正意义上的克隆,也就是深拷贝。
2.通过对象序列化实现深拷贝
Dog类
package com.jc.prototype;
import java.io.*;
/**
* @program: 设计模式
*
* @description:
*
* @author: Mr.Wang
*
* @create: 2021-06-13 19:34
**/
public class Dog implements Serializable{
private int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Sheep类
package com.jc.prototype;
import java.io.*;
/**
* @program: 设计模式
* @description:
* @author: Mr.Wang
* @create: 2021-06-13 18:12
**/
public class Sheep implements Serializable {
private int age;
private char sex;
private Dog friend;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public Dog getFriend() {
return friend;
}
public void setFriend(Dog friend) {
this.friend = friend;
}
public Sheep(int age, char sex, Dog friend) {
this.age = age;
this.sex = sex;
this.friend = friend;
}
public Sheep(){
}
public Sheep deepclone(){
ByteArrayOutputStream bos = null;
ByteArrayInputStream bis = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);//把当前这个对象以对象流方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);//反序列化
Sheep clone = (Sheep) ois.readObject();
bos.flush();
oos.flush();
return clone;
}catch (Exception e){
e.printStackTrace();
return null;
}finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public String toString() {
return "Sheep{" +
"age=" + age +
", sex=" + sex +
", friend=" + friend +
'}';
}
}
Client类
package com.jc.prototype;
import java.util.function.DoubleFunction;
/**
* @program: 设计模式
* @description:
* @author: Mr.Wang
* @create: 2021-06-13 18:21
**/
public class Client {
public static void main(String[] args) {
Dog friend = new Dog(3);
Sheep sheep = new Sheep(22,'男',friend);
Sheep clone = sheep.deepclone();
//拷贝完了 如果我们在对Dog进行修改的话 clone后的对象还是会跟着更改,因为他们指向的都是friend实例
friend.setAge(5);
System.out.println("sheep' hashcode:"+sheep.hashCode()+", "+sheep+", friend' hashcode:"+sheep.getFriend().hashCode());
System.out.println("clone' hashcode:"+clone.hashCode()+", "+clone+", friend' hashcode:"+clone.getFriend().hashCode());
/**
* 打印结果
*sheep' hashcode:2133927002, Sheep{age=22, sex=男, friend=Dog{age=5}}, friend' hashcode:1836019240
*clone' hashcode:381259350, Sheep{age=22, sex=男, friend=Dog{age=3}}, friend' hashcode:2129789493
*/
}
}
通过打印结果可以看出来完成了深拷贝。
3.在spring源码中引用
test方法
@Test
public void test2(){
AnnotationConfigApplicationContext ApplicationContext =
new AnnotationConfigApplicationContext(txconfig.class);
System.out.println("容器初始化完成");
Dog dog =(Dog) ApplicationContext.getBean("dog");
System.out.println("dog' hashcode:"+dog.hashCode());
Dog dog1 =(Dog) ApplicationContext.getBean("dog");
System.out.println("dog1' hashcode:"+dog1.hashCode());
ApplicationContext.close();
/**Scope=prototype
*
* @Bean
* @Scope("prototype")
* public Dog dog(){
* return new Dog();
* }
* 打印结果
* dog' hashcode:2134607032
* dog1' hashcode:2134607032
*/
/**Scope=singleton
*
* @Bean
* @Scope("singleton")
* public Dog dog(){
* return new Dog();
* }
* 打印结果
*dog' hashcode:2134607032
*dog1' hashcode:1470344997
*/
}
通过hashcode可以看出来,当Scope=prototype,即原型模式创建对象;当Scope=singleton时,即单例模式创建对象。
debug追踪一下,发现在AbstractFactory中的doGetBean方法中有一段这样代码
if (mbd.isSingleton()) {//如果是单例模式的话,创建单例。
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {//如果是原型模式的话,创建一个原型实例。
var12 = null;
Object prototypeInstance;
try {
this.beforePrototypeCreation(beanName);
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}
beanInstance = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
通过if (mbd.isSingleton())和else if (mbd.isPrototype())来判断是通过单例模式创建对象还是原型模式创建对象。
总结
好
1.相比于之前我们复制对象,原型设计模式效率更高。
2.如果原型对象发生变化,克隆的对象也会发生变化,无需修改代码。(浅克隆)
不好
1.实现深克隆的时候可能需要比较复杂的代码
2需要为每一个类配置一个克隆方法,对后期拓展的类不难,,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。