原型体2攻略
————— 第二天 —————
————————————
如果有一天,小灰被外星人抓住了,外星人应该用小灰做实验,想知道小灰在吃得好、睡得好、玩得开心的场景中和小灰的生活状态有什么区别。
因此,外星人克隆了几个完全相同的小灰:
这样,小灰的原型就留在了现实中,三种复制体提供了三种不同的环境:吃得好、睡得好、玩得开心。小灰的原型不受三种复制体的影响。
过了一段时间,我们来观察一下本体和分离的生活状态:
在Java语言中,Object类实现了Cloneable接口,一个对象可以通过调用Clone()来生成对象,这是原型模式的典型应用。
但需要注意的是,clone()方法不在cloneable接口中,而在object类中。cloneable是一个标识接口,可以复制标识的对象。如果cloneable接口没有实现,但是clone()方法被调用,就会报错。
// protected native Object clone() throws
CloneNotSupportedException;protected Object clone() throws
CloneNotSupportedException { if (!(this instanceof Cloneable)) { throw new CloneNotSupportedException(
"Class " getClass().getName()
" doesn't implement Cloneable"); } return internalClone();}// Native helper method for cloning.private native Object internalClone();
Java中的数据类型分为基本类型和参考类型。如果一种方法中的变量是基本类型,则变量直接存储在该方法的栈帧中,如int、long等;引用类型将变量指针存储在栈帧中,指向堆中实体的地址,如String、Array等。深拷贝和浅拷贝只针对引用的数据类型。
例如,一种方法有一个基本类型参数和一个参考类型参数。在方法体中重新赋值参数会影响引入的参考类型参数,而不会影响基本类型参数,因为基本类型参数是值传输,而参考类型是参考传输。
首先定义用户类:
// 这是一个非常简单的用户类 public class User { private String name; private int age; public User(String name, int age) { this.name=name; this.age=age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{name='" name ", age=" age '}'; }}
测试:
private int x=10; public void updateValue(int value){ value = 3 * value;} private User user= new User("大黄",20); public void updateUser(User student){ student.setName("小灰"); student.setAge(18);} public void test(){ System.out.println("调用前x值:" x); updateValue(x); System.out.println("调用后x的值:" x); System.out.println("调用前user值:" user.toString()); updateUser(user); System.out.println("调用后user值:" user.toString());}
Log打印结果如下:
调用前x值:10
调用后x值:10
调用前user的值:User{name='大黄, age=20}
调用后user的值:User{name='小灰, age=18}
传递基本类型的方法(updateValue()流程图:
传递引用类型的方法(updateUser()流程图:
这也包括例外,如String类型和大小不超过127的Long类型。虽然它们也是参考类型,但它们不受基本类型的影响。这是因为他们将首先比较常量池维护的值,这涉及到VM的内容。今天我不会讨论太多。
浅拷贝是按位的(bit)复制对象,该对象具有原始对象属性值的精确复制。让我们结合应用场景进行分析,或者刚才的优步。我们添加了存储地址的内部Address。我们需要用户信息可以被其他module查询,但不允许被其他module修改。新代码如下:
// 这是一个稍微复杂的用户类,支持复制 public class User implements Cloneable {
// …省略上面的代码… private Address address; @NonNull @NotNull @Override public User clone() { try{ return (User)super.clone(); }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address{ // 地市 public String city; // 区县 public String county; // 乡镇街道 public String street; } }
// 这是一个更复杂的用户类,支持深度复制 public class User implements Cloneable { // …省略上面的代码… @NonNull @NotNull @Override public User clone() { try{ User newUser = (User)super.clone(); newUser.setName(this.name); newUser.setAddress(this.address.clone()); return newUser; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address implements Cloneable{ // …省略上面的代码… @NonNull @NotNull @Override public Address clone() { try{ Address newAddress = (Address)super.clone(); newAddress.city = this.city; newAddress.county = this.county; newAddress.street = this.street; return newAddress; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } }}
需要注意的是,上述代码的深度复制并不完全,因为完全的深度复制几乎是不可能的,这不仅可能是非常复杂的引用关系,而且可能是引用链的第三方对象没有实现第三方对象的Cloneable接口。
绝大多数设计模式牺牲性能来提高开发效率,而原型模式是为数不多的牺牲开发效率来提高性能的设计模式之一。
private User user= new User("大黄",20); public void testNew(){ User user1 = new User("小灰",18);} public void testClone(){ User user2 = user.clone();}
通过ASM工具查看bytecode,可以看到两者对栈资源的消耗:
// access flags 0x1
public testNew()V
…省略…
MAXSTACK = 4
MAXLOCALS = 2
// access flags 0x1
public testClone()V
…省略…
MAXSTACK = 1
MAXLOCALS = 2
@Override public Object clone() { return new Intent(this); }
最后,总结原型模式的核心用途:
1.解决构建复杂对象的资源消耗问题,提高创建对象的效率。
2.保护性拷贝,防止外部需要修改只读对象。