一, 引用的复制和对象复制.
在编程中, 我们有时会用两个引用指向同一个对象.
例如:
ArrayList a = new ArrayLIst();
ArrayList b = a;
看起来好像有a,b两个容器, 实际上a,b是两个引用, 它们都指向同1个Object的内存地址.
而对象复制是指:
在内存里划分一块与指定对象相同内容的内存.
也就是说内存里原理有1个Object的内存, 复制后就有两个了...
二, 对象复制的简便方法.
当然, 对象复制的实现十分简单, 只需要实例化1个具有相同属性的对象就ok了.
注意, 如果用 "==" 来讲比较两个引用是返回false, 因为它们的地址不同.
举个列子:
2.1 产品类Prod:
public class Prod {
private int prodID;
private String prodName;
public int getProdID() {
return prodID;
}
public void setProdID(int prodID) {
this.prodID = prodID;
}
public String getProdName() {
return prodName;
}
public void setProdName(String prodName) {
this.prodName = prodName;
}
@Override
public String toString(){
return "Prod: " + this.getProdID() + " , " + this.getProdName();
}
}
2.2 订单类Order:
public class Order {
private int orderID;
private Prod prod;
private int amount;
public int getOrderID() {
return orderID;
}
public void setOrderID(int orderID) {
this.orderID = orderID;
}
public Prod getProd() {
return prod;
}
public void setProd(Prod prod) {
this.prod = prod;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String toString(){
return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
}
}
2.3 客户端代码:
这里我先实例化1个order类 od1, 然后在复制1个od1 的对象od2
Prod p1 = new Prod();
p1.setProdID(1);
p1.setProdName("Hammer");
Order od1 = new Order();
od1.setOrderID(1);
od1.setProd(p1);
od1.setAmount(20);
Prod p2 = new Prod();
p2.setProdID(1);
p2.setProdName("Hammer");
Order od2 = new Order();
od2.setOrderID(1);
od2.setProd(p1);
od2.setAmount(20);
System.out.println(od1);
System.out.println(od2);
输出:
Order: 1 , 20 Prod: 1 , Hammer
Order: 1 , 20 Prod: 1 , Hammer
2.4 这种方法的缺点
首先这种写法不够优雅, 很多重复代码.
其次, 这种复制方法调用了类的构造函数.
就如上面的例子, 即使Order类的构造函数是空的, 但是实际上构造函数的执行机制很复杂,.
构造函数最重要的动作就是给类里的每个成员划分内存空间. 毕竟java是类c语言, 相当于执行了很多次malloc()函数.
如果某些业务类的构造函数写的很复杂, 就更耗费资源了.
而且
三, 利用Object.clone()方法来实现对象复制.
java里所有对象都是直接或简直继承自基类Object.
而clone()是基类的Native 方法, 所谓Native方法可以认为是java 内部特定方法, 它比非Native的执行效率要好.
也就是说, 利用Object.clone() 方法会比实例化1个相同内容对象效率要好.
3.1 Object.clone()方法和 Cloneable接口:
Object.clone() 方法有点特殊, 首先它是public方法, 也就是说它可以通过引用名和"."来调用.
但是它不能被子类继承, 也就说, 出了Object类外所有java里的class里面都没有clone()这个方法的.
如果要使用Object.clone()方法.
只能引用1个叫做 Cloneable里的Interface, 然后重写Cloneable接口里的clone()方法, 在里面调用object.clone().
这个接口就起了1个标记的作用, 帮组实现多态.
3.2 订单类Order:
我们按在这个思路来修改上面的订单类Order:public class Order implements Cloneable{
private int orderID;
private Prod prod;
private int amount;
public int getOrderID() {
return orderID;
}
public void setOrderID(int orderID) {
this.orderID = orderID;
}
public Prod getProd() {
return prod;
}
public void setProd(Prod prod) {
this.prod = prod;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String toString(){
return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
}
@Override
public Object clone(){
Object o = null;
try{
o = super.clone();
}catch(Exception e){
e.printStackTrace();
}
return o;
}
}
值得注意的是, Object.clone() 回抛异常.
而, Prod类没有任何修改.
3.3 客户端代码:
Prod p1 = new Prod();
p1.setProdID(2);
p1.setProdName("knife");
Order od1 = new Order();
od1.setOrderID(2);
od1.setAmount(30);
od1.setProd(p1);
Order od2 = (Order)od1.clone();
System.out.println(od1);
System.out.println(od2);
可见, 只需要执行一句
Order od2 = (Order)od1.clone();
就相当于 复制了1个对象.
3.4 UML图
我们来看看这个例子的UML:
实际上这个就是原型模式(prototype)的UML图了,
原来我们不知不觉得使用了原型模式.
四, 原型模式的定义
原型模式(protoType), 用原型实例制定创建对象的种类, 并且通过copy这些原型创建新的对象.
总觉得设计模式的定义都太过于简单恶心了.
上面的
原型指的是 Cloneable这个接口.
原型实例指的是 Order这个类.
五, 浅复制和深复制
对象复制也分两种.
两个对象中的值类型成员属性相等, 对象成员成员属性实际上没有复制,都是同一个对象.
深复制:
两个对象中的值类型成员属性相等, 对象成员属性指向不同的 具有相同成员的 对象.
如上面的例子, 稍稍修改客户端代码:
Prod p1 = new Prod();
p1.setProdID(2);
p1.setProdName("knife");
Order od1 = new Order();
od1.setOrderID(2);
od1.setAmount(30);
od1.setProd(p1);
Order od2 = (Order)od1.clone();
System.out.println(od1);
System.out.println(od2);
od1.setOrderID(3);
od1.setAmount(40);
od1.getProd().setProdName("blade");
System.out.println(od1);
System.out.println(od2);
上面 构建了对象od1,
然后复制出了另1个对象od2,
然后修改od1的 值成员 和 对象成员
然后输出od1, 和 od2 成员的值:
Order: 2 , 30 Prod: 2 , knife
Order: 2 , 30 Prod: 2 , knife
Order: 3 , 40 Prod: 2 , blade
Order: 2 , 30 Prod: 2 , blade
可见, 当od1 的id 和 amount的值被改成 3 和 40 后, od2 的id和 Amount的值并不受影响, 因为它们的值类型成员属性是相互独立的.
但是当od1 的 prod属性的内容被修改后, od2 的也被修改了(knife - > blade), 因为 od1 和 od2 的 prod成员指向的都是同1个对象.
这就证明了:
Java里的 Object.clone()方法实现的是浅复制.
六, 原型模式的深复制实现
接下来再次修改上面的例子, 令其实现成深复制
而且, 根据项目需要, 复制并不会复制Order的id, 也就是订单号码是唯一的, 只复制Prod和数量.
6.1 订单类Order:
public class Order implements Cloneable{
private int orderID;
private Prod prod;
private int amount;
public int getOrderID() {
return orderID;
}
public void setOrderID(int orderID) {
this.orderID = orderID;
}
public Prod getProd() {
return prod;
}
public void setProd(Prod prod) {
this.prod = prod;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String toString(){
return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
}
@Override
public Object clone(){
Prod p = new Prod();
p.setProdID(this.getProd().getProdID());
p.setProdName(this.getProd().getProdName());
Order o = new Order();
o.setOrderID(this.getOrderID() + 1);
o.setAmount(this.getAmount());
o.setProd(p);
return o;
}
}
客户端输出:
Order: 2 , 30 Prod: 2 , knife
Order: 3 , 30 Prod: 2 , knife
Order: 3 , 40 Prod: 2 , blade
Order: 3 , 30 Prod: 2 , knife
可以, 无论od1修改了什么, od2 都不受影响
这种方法放弃了Object.clone(), Order类在重写clone()方法里还是使用了构造方法去实例化1个新的对象.
这种方法配置更加灵活(选择性地复制成员)
当时放弃了Object.clone()的效能优点.
但是这仍然实现了原型模式(protoType)