原型模式
所谓原型模式,就是以一个对象为原型,返回这个对象的克隆,而不是创建一个新的对象
原型对象的优点
在Java中创建一个新对是昂贵和资源密集型, 原型对象有助于创建更好性能的重复对象
原型模式的核心就是克隆,克隆又分为浅克隆,深克隆
1、为什么分为浅克隆,深克隆
因为在克隆的时候引用类型不一定会被克隆,浅克隆和深克隆的区别在于,是否克隆了原型对象中 的引用类型。
2、那么什么叫做引用类型
在这里简单的说一下引用类型,引用类型是在Java中除八大基础类型(int,long,float,byte,char,boolean,short,double)之外的其他类型
示例
一个空的用户类,一个客户类,客户中含有用户对象
浅克隆
//用户类
public class User(){}
//客户类
public class Client implements Cloneable {
private String id;
private User user;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Client clone(){
Client clone=null;
try{
clone=(Client)super.clone();
}catch (Exception e){
e.printStackTrace();
}
return clone;
}
}
1、为什么要继承Cloneable
Cloneable只是一个空接口,在Java中起一个标记的作用,只有实现这个接口后,然后在类中重写Object的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常
2、Object中的clone方法是一个空方法,如何判断类是否实现了Cloneable接口
首先我们看Object中clone方法的源码
protected native Object clone() throws CloneNotSupportedException;
原因在于native这个关键字修饰。
native修饰的方法都是空的,但是这些方法都是有实现体的(在这里间接的说明的native关键字不能和abstract同时使用,因为abstract修饰的方法与Java的接口中的方法类似,他显式的说明了修饰的方法在当前的是没有实现的,abstract修饰的方法的实现都是在子类中实现的),只不过native方法的实现体,都是非Java代码编写的(例如:调用的jvm中C编写的接口),每一个native在jvm方法中都有一个同名的实现体,nvtive方法在逻辑上的判断都是由实现体实现的,另外这种native修饰的方法对返回类型、异常控制等都没有约束
测试类
public class Main {
public static void main(String[] args) {
//测试浅克隆
Client client1=new Client();
User user=new User();
client1.setId("1");
client1.setUser(user);
Client client2=client1.clone();
System.out.println("克隆对象的内存地址是否相等"+(client2==client1));
System.out.println("克隆对象的引用类型的内存地址是否相等"+(client2.getUser()==client1.getUser()));
}
}
测试结果
克隆对象的内存地址是否相等false
克隆对象的引用类型的内存地址是否相等true
1、为什么克隆对象的内存地址是不相等的
因为调用Object类的clone方法之后,会在堆中重新分配一块内存给新产生的对象,
所以他们在堆中的内存地址是不相同的(JVM中堆、栈介绍)
2、为什么克隆对象的引用类型的内存地址是相等的
因为调用Object类中的clone方法后,新产生的对象中的基础类型的属性的值与原型对象是一致的,而引用类型的值也是一致的,但并不代表在克隆对象的时候,也同时克隆了原型对象的引用类型,之所以克隆对象的引用类型的内存地址是相等的,就是因为新产生的对象中的引用类型并没有克隆,依然还是指向原型对象中的引用类型的内存地址
深克隆
深克隆实现的方式有两种
一:调用Object类中的clone方法,同时代码中手动克隆引用类型
二:通过Java序列化技术
第一种
public class User implements Cloneable {
public User clone(){
User user=null;
try{
user=(User) super.clone();
}catch (Exception e){
e.printStackTrace();
}
return user;
}
}
因为User无clone方法,需手动添加
public class Client implements Cloneable{
private String id;
private User user;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Client clone(){
Client clone=null;
try{
clone=(Client)super.clone();
// 手动克隆引用类型
clone.user=this.user.clone();
}catch (Exception e){
e.printStackTrace();
}
return clone;
}
}
测试类
public class Main {
public static void main(String[] args) {
//测试浅克隆
Client client1=new Client();
User user=new User();
client1.setId("1");
client1.setUser(user);
Client client2=client1.clone();
System.out.println("克隆对象的内存地址是否相等"+(client2==client1));
System.out.println("克隆对象的引用类型的内存地址是否相等"+(client2.getUser()==client1.getUser()));
}
}
测试结果
克隆对象的内存地址是否相等false
克隆对象的引用类型的内存地址是否相等false
1、为什么克隆对象的引用类型的内存地址不相等了
因为在原型对象的克隆方法中,手动的添加了引用类型的克隆方法,使得新产生的对象的引用类型所指向的内存地址为一个新的内存地址,所以克隆对象的引用类型的内存地址不想等了
第二种
//用户类
public class User implements Serializable {}
1、为什么用户类要继承Serializable接口
Serializable接口与Cloneable类似,都是一个空接口,起一个标识作用,表示该对象可序列化,如果不继承该接口就执行序列化操作,则抛出NotSerializableException异常
而User只是一个空对象,为何需要继承呢。因为User在Client中是引用的类型,如果要序列化Client对象,则其引用类型都要可执行序列化,不然将抛出NotSerializableException异常
//客户类
public class Client implements Serializable {
private String id;
private User user;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
/**
* 使用序列化技术克隆,深克隆
*/
public Client cloneClient() throws IOException, ClassNotFoundException {
//将对象写入流中
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return (Client) ois.readObject();
}
}
测试类
public class Main {
public static void main(String[] args) {
//测试深克隆
Client client3 = new Client();
User user2 = new User();
client3.setId("1");
client3.setUser(user2);
Client client4 = null;
try {
client4 = client3.cloneClient();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("克隆对象的内存地址是否相等" + (client4 == client3));
System.out.println("克隆对象的引用类型的内存地址是否相等" + (client4.getUser() == client3.getUser()));
}
}
测试结果
克隆对象的内存地址是否相等false
克隆对象的引用类型的内存地址是否相等false
使用序列化技术克隆时,无论是原型对象还是原型对象的引用类型,都是重新克隆一遍,所以内存地址都不相等