初识原型模式
为什么用
- 类初始化消耗太多资源(数据、硬件),为了避免消耗
- 复杂对象
- 给其他对象访问,而且调用者可能需要修改值
- 访问复杂
生活示例
以文档的拷贝为例,文档中含有文本和图片。为了安全,我们需要将原来的文档拷贝一份副本,在副本上进行修改。
用法
- 实现clone方法
- 类实现接口Cloneable
Cloneable接口只是标记接口(空实现),如果实现了clone方法,没有实现接口会出异常
Object中的clone方法
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
return internalClone();
}
方法实现
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
举个例子(浅拷贝)
这是浅拷贝
public class User implements Cloneable {
private String name = "1";
private int age = 2;
public User(){
System.out.println("----Constructor method----");
}
@Override
protected User clone() {
try {
User user = (User) super.clone();
user.name = this.name;
user.age = this.age;
return user;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public void setUser(String name, int age) {
this.name = name;
this.age = age;
}
public String toStringUser() {
return "User{" +
"name='" + name + '\'' +
", age=" + age;
}
}
输出一下
public static void main(String[] args) {
User user = new User();
//1
System.out.println(user.toStringUser());
User u2 = user.clone();
u2.setUser("123", 12231);
//1
System.out.println(u2.toStringUser());
//2
System.out.println(user.toStringUser());
}
结果
----Constructor method----
User{name='1', age=2}
User{name='123', age=12231}
User{name='1', age=2}
总结一下浅拷贝
- 类变量都是基本数据类型
- clone方法生成的对象没有调用构造方法
- 不调用构造方法会导致某些初始化无法使用
- 构造方法产生的对象改变值,并不改变原对象值
举个例子(错误例子)
public class User implements Cloneable {
private String name = "1";
private int age = 2;
private ArrayList<Integer> mList=new ArrayList<>();
public User(){
System.out.println("----Constructor method----");
}
@Override
protected User clone() {
try {
User user = (User) super.clone();
user.name = this.name;
user.age = this.age;
user.mList = this.mList;
return user;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public void setUser(String name, int age) {
this.name = name;
this.age = age;
}
public void addList(int i) {
this.mList.add(i);
}
public String toStringUser() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", mList=" + mList +
'}';
}
}
输出一下
public static void main(String[] args) {
User user = new User();
user.addList(2);
System.out.println(user.toStringUser());
User u2 = user.clone();
u2.setUser("123", 12231);
u2.addList(3);
System.out.println(u2.toStringUser());
System.out.println(user.toStringUser());
}
结果
----Constructor method----
User{name='1', age=2, mList=[2]}
User{name='123', age=12231, mList=[2, 3]}
User{name='1', age=2, mList=[2, 3]}
发现一下原因
经过上面的分析浅拷贝,会发现,List是引用类型,那我们大体上也可以了解clone实现的原理了,官方说是这样实现的
在内存中二进制流的拷贝
我们最直观的感受就是拷贝的对象引用了原始对象的字段,因为基本类型存的是值,引用类型存的是地址,和adapter是一样的,我们修改A时B也会改变,因为他们指向同一个地址。
那我们怎么办?为了实现原型模式的作用–深拷贝,我们可以验证一下是否正确
测试一下
public class User implements Cloneable {
private String name = "1";
private int age = 2;
private ArrayList<Integer> mList=new ArrayList<>();
public User(){
System.out.println("----Constructor method----");
}
@Override
protected User clone() {
try {
User user = (User) super.clone();
user.name = this.name;
user.age = this.age;
user.mList = this.mList;
return user;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public void setUser(String name, int age) {
this.name = name;
this.age = age;
}
public void setList(ArrayList<Integer> mList) {
this.mList = mList;
}
public String toStringUser() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", mList=" + mList +
'}';
}
}
输出一下
public static void main(String[] args) {
User user = new User();
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
user.setList(list);
//1
System.out.println(user.toStringUser());
User u2 = user.clone();
u2.setUser("123", 12231);
list=new ArrayList<>();
list.add(3);
u2.setList(list);
//2
System.out.println(u2.toStringUser());
//1
System.out.println(user.toStringUser());
}
结果
----Constructor method----
User{name='1', age=2, mList=[1, 2]}
User{name='123', age=12231, mList=[3]}
User{name='1', age=2, mList=[1, 2]}
结论
以上就是正确的,但是我们不能每次都如此麻烦,深拷贝就是这样么?不是的,我们在初始化时要把引用类型字段也拷贝,而不是单纯的引用。
举例(深拷贝)
public class User implements Cloneable {
private String name = "1";
private int age = 2;
private ArrayList<Integer> mList=new ArrayList<>();
public User(){
System.out.println("----Constructor method----");
}
@Override
protected User clone() {
try {
User user = (User) super.clone();
user.name = this.name;
user.age = this.age;
user.mList = (ArrayList<Integer>) this.mList.clone();
return user;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public void setUser(String name, int age) {
this.name = name;
this.age = age;
}
public void addList(int i) {
this.mList.add(i);
}
public String toStringUser() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", mList=" + mList +
'}';
}
}
输出一下
User user = new User();
user.addList(1);
user.addList(2);
System.out.println(user.toStringUser());
User u2 = user.clone();
u2.setUser("123", 12231);
u2.addList(3);
System.out.println(u2.toStringUser());
System.out.println(user.toStringUser());
结果
User{name='1', age=2, mList=[1, 2]}
User{name='123', age=12231, mList=[1, 2, 3]}
User{name='1', age=2, mList=[1, 2]}