点赞再看,养成习惯,公众号搜一搜【一角钱技术】关注更多原创技术文章。
本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章。
前言
- 23种设计模式速记
- 单例(singleton)模式
- 工厂方法(factory method)模式
- 抽象工厂(abstract factory)模式
- 建造者/构建器(builder)模式
23种设计模式快速记忆的请看上面第一篇,本篇和大家一起来学习原型模式,在学习原型模式之前我们需要先认识下浅拷贝和深拷贝这两个概念。
浅拷贝和深拷贝
浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化。
深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变。
模式定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式其实就是一个对象在创建另一个可定制的对象,而且不需要指定任何创建的细节。Java提供了Coneable接口,其中有一个唯一方法Clone(),实现这个接口就可以完成原型模式了。
实例说明
浅拷贝案例
声明 User
实体类,需要实现 Cloneable
接口,并覆写 clone()
方法。
User 属性包括基础数据类型和引用数据类型,方便演示
package com.niuh.designpattern.prototype;
/**
* 用户信息
*/
public class User implements Cloneable {
// 基础数据类型
private int id;
private String name;
private String sex;
private String pwd;
// 引用数据类型
private BaseInfo baseInfo;
public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) {
this.id = id;
this.name = name;
this.sex = sex;
this.pwd = pwd;
this.baseInfo = baseInfo;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public BaseInfo getBaseInfo() {
return baseInfo;
}
public void setBaseInfo(BaseInfo baseInfo) {
this.baseInfo = baseInfo;
}
@Override
public String toString() {
return "hashCode: " + super.hashCode() + ", User{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", pwd='" + pwd + '\'' +
", baseInfo=" + baseInfo +
'}';
}
@Override
protected User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}
package com.niuh.designpattern.prototype;
import java.util.Date;
/**
* 基础类
*/
public class BaseInfo {
private String desc;
// .......
public BaseInfo(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "BaseInfo{" +
"desc=" + desc +
'}';
}
}
package com.niuh.designpattern.prototype;
/**
* 原型设计模式
*/
public class PrototypePattern {
public static void main(String[] args) throws CloneNotSupportedException {
BaseInfo baseInfo = new BaseInfo("张三");
User user1 = new User(1, "张三", "男", "123456", baseInfo);
// new User ......
// 克隆机制
User user2 = user1.clone();
user2.setId(2);
user2.setName("李四");
BaseInfo baseInfo1 = user2.getBaseInfo();
baseInfo1.setDesc("李四");
System.out.println(user1);
System.out.println(user2);
}
}
输出结果如下:
由输出的结果可见,通过
user1.clone()
拷贝对象后得到的user2
,和user1
是两个不同的对象,HashCode
值不一样。user1
和user2
的基础数据类型的修改互不影响,而引用类型baseInfo
修改后是会有影响的。
深拷贝案例
通过上面的例子可以看到,浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了 user2
的 baseInfo
,但是 user1
的 baseInfo
也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。
深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。
对于 User
的引用类型的成员变量 BaseInfo
,需要实现 Cloneable
并重写 clone()
方法。
package com.niuh.designpattern.prototype;
import java.util.Date;
/**
* 基础类
*/
public class BaseInfo implements Cloneable {
private String desc;
// .......
public BaseInfo(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "BaseInfo{" +
"desc=" + desc +
'}';
}
@Override
protected BaseInfo clone() throws CloneNotSupportedException {
//BaseInfo 如果也有引用类型的成员属性,也应该和 User 类一样实现
return (BaseInfo) super.clone();
}
}
在 User
的 clone()
方法中,需要拿到拷贝自己后产生的新的对象,然后对新的对象的引用类型再调用拷贝操作,实现对引用类型成员变量的深拷贝。
package com.niuh.designpattern.prototype;
/**
* 用户信息
*/
public class User implements Cloneable {
// 基础数据类型
private int id;
private String name;
private String sex;
private String pwd;
// 引用数据类型
private BaseInfo baseInfo;
public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) {
this.id = id;
this.name = name;
this.sex = sex;
this.pwd = pwd;
this.baseInfo = baseInfo;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public BaseInfo getBaseInfo() {
return baseInfo;
}
public void setBaseInfo(BaseInfo baseInfo) {
this.baseInfo = baseInfo;
}
@Override
public String toString() {
return "hashCode: " + super.hashCode() + ", User{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", pwd='" + pwd + '\'' +
", baseInfo=" + baseInfo +
'}';
}
@Override
protected User clone() throws CloneNotSupportedException {
// 深拷贝
User user = (User) super.clone();
user.baseInfo = baseInfo.clone();
return user;
}
}
与上面的使用方式一样,输出结果如下:
由输出结果可见,深拷贝后,不管是基础数据类型还是引用类型的成员变量,修改其值都不会相互造成影响。
序列化机制实现深拷贝
需要在 User
类实现 Serializable
,成员类型(BaseInfo)也需要实现 Serializable
接口。
package com.niuh.designpattern.prototype;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 用户信息
*/
public class User implements Cloneable , Serializable {
// 基础数据类型
private int id;
private String name;
private String sex;
private String pwd;
// 引用数据类型
private BaseInfo baseInfo;
public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) {
this.id = id;
this.name = name;
this.sex = sex;
this.pwd = pwd;
this.baseInfo = baseInfo;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public BaseInfo getBaseInfo() {
return baseInfo;
}
public void setBaseInfo(BaseInfo baseInfo) {
this.baseInfo = baseInfo;
}
@Override
public String toString() {
return "hashCode: " + super.hashCode() + ", User{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", pwd='" + pwd + '\'' +
", baseInfo=" + baseInfo +
'}';
}
@Override
protected User clone() throws CloneNotSupportedException {
// 深拷贝
// User user = (User) super.clone();
// user.baseInfo = baseInfo.clone();
// return user;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream)) {
oos.writeObject(this);
} catch (IOException e) {
e.printStackTrace();
}
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
try (ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream)) {
try {
User user = (User) ois.readObject();
return user;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
这个时候并没有使用Java深拷贝,改变成员属性Baseinfo,也能保存对象的独立性。
通过序列化机制来完成深拷贝不推荐使用,因为序列化操作是CPU密集型,解析流是比较消耗性能,速度会比较慢
优点
- 可以不耦合具体类的情况下克隆对象
- 避免重复的初始化代码
- 更方便的构建复杂对象
缺点
- 适用性不是很广。
- 每一个类必须配备一个克隆方法。
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
应用场景
当代码不应该依赖于需要复制的对象的具体类时,请使用Prototype模式。
- 某些结构复杂的对象的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口;
- 一般在初始化的信息不发生变化的情况下,克隆是最好的方法。
源码中的应用
#Spring
org.springframework.beans.factory.support.AbstractBeanDefinition
#JDK
java.util.Arrays
java.util.ArrayList
......
ArrayList中的使用
ArrayList也有clone()方法,如下
- 返回一个Object对象,所以在使用此方法的时候要强制转换。
- ArrayList的本质是维护了一个Object的数组,所以克隆也是通过数组的复制实现的,属于浅复制。
@Override
public Object clone() {
try {
ArrayList> result = (ArrayList>) super.clone();
result.array = array.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
ArrayList的Clone浅复制的巧妙使用
当你需要使用remove方法移除掉集合中的对象,而非要修改集合中的对象的时候,可以选择使用。
//添加两个元素
Student stJack=new Student("Jack", 13);
Student stTom=new Student("Tom", 15);
list.add(stJack);
list.add(stTom);
//克隆
ArrayList listCopy=(ArrayList) list.clone();
//移除且不修改
listCopy.remove(1);
System.out.println(list);
System.out.println(listCopy);
移除且不修改集合中的元素,只是在List内部的数组中移除了指向元素的地址,可以放心的使用clone。
PS:以上代码提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git
文章持续更新,可以公众号搜一搜「 一角钱技术 」第一时间阅读。
本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。