java对象 clone_Java对象复制--慎用clone

什么是拷贝、影子拷贝、深度拷贝,不是本文要讨论的。如需了解,以下两个连接还是不错滴。

1.clone的优点

a. 获得一个对象的拷贝(此处指深层拷贝)使用赋值操作符“=”是不能完成的;

b. 无需调用构造函数即可获得对象的拷贝(当然,拷贝对象和被克隆对象之间是否影响取决于深克隆还是浅克隆),一定程度上可以提高执行效率。

2.clone的缺点

以下将根据一个具体的例子来说明这个问题,当然这里指的是深层拷贝。

Car.java -- 父类,没有公开clone方法

package com.clonedemo.test;

public class Car {

private String type; // 型号

private String manufacturer; //制造商

private Engine engine; // 引擎

public Car(String type, String manufacturer, Engine engine) {

super();

this.type = type;

this.manufacturer = manufacturer;

this.engine = engine;

}

public String getType() {

return type;

}

public void setType(String type) {

this.type = type;

}

public String getManufacturer() {

return manufacturer;

}

public void setManufacturer(String manufacturer) {

this.manufacturer = manufacturer;

}

public Engine getEngine() {

return engine;

}

public void setEngine(Engine engine) {

this.engine = engine;

}

// car common methods such as drive,start,stop definition, omitted

// ...

}

RaceCar.java -- 继承自Car类,为其子类,提供公开的clone方法

package com.clonedemo.test;

public class RaceCar extends Car implements Cloneable {

private String speeder;

// other variables, omitted

// ...

public RaceCar(String type, String manufacturer, Engine engine,

String speeder) {

super(type, manufacturer, engine);

this.speeder = speeder;

}

@Override

protected Object clone() throws CloneNotSupportedException {

RaceCar racecar = (RaceCar) super.clone();

racecar.speeder = speeder;

return racecar;

}

public String getSpeeder() {

return speeder;

}

public void setSpeeder(String speeder) {

this.speeder = speeder;

}

// race car methods definition, omitted

// ...

}

Engine.java -- 引擎类,没有公开clone方法

package com.clonedemo.test;

public class Engine {

private String type; // 引擎型号

private Integer power; // 马力

public Engine(String type, Integer power) {

super();

this.type = type;

this.power = power;

}

public String getType() {

return type;

}

public void setType(String type) {

this.type = type;

}

public Integer getPower() {

return power;

}

public void setPower(Integer power) {

this.power = power;

}

}

CloneTester.java -- 测试类

package com.clonedemo.test;

public class CloneTester {

/**

* @param args

* @throws CloneNotSupportedException

*/

public static void main(String[] args) throws CloneNotSupportedException {

RaceCar r1 = new RaceCar("BMW-X7", "BMW", new Engine("RR-X7", 500),

"speeder-001");

RaceCar r2 = (RaceCar) r1.clone();

r2.setManufacturer("GE");

System.out.println("R1 Manufacturer: " + r1.getManufacturer());

System.out.println("R2 Manufacturer: " + r2.getManufacturer());

r2.getEngine().setType("RR-X8");

System.out.println("R1 Engine Type: " + r1.getEngine().getType());

System.out.println("R2 Engine Type: " + r2.getEngine().getType());

}

}

输出结果

R1 Engine Type: RR-X8

R2 Engine Type: RR-X8

R1 Manufacturer: BMW

R2 Manufacturer: GE

观察输出结果可以发现,R1和R2的引擎引用的是同一个对象,原因是我们没有为RaceCar的父类实现公开的clone。

但为什么同是对象类型(String)的Manufacturer却不是指向同一个对象呢? 事实上,你会发现基本类型int,double等对应的Integer,Double等对象在这种情况下和String类型一样,实现了深度克隆的效果。原因在于,这些对象被设计为不可更改的类(immutable class),即一旦这个类初始化,那么类中的函数都不能改变自身的值,而是返回修改后的对象。当执行r2 = r1.clone()后,r1的manufacturer和r2的manufacturer指向的是同一个String对象,这可以通过如下代码证实:

RaceCar r1 = new RaceCar("BMW-X7", "BMW", new Engine("RR-X7", 500), "speeder-001");

RaceCar r2 = (RaceCar) r1.clone();

System.out.println(r1.getManufacturer() == r2.getManufacturer());

输出: true

当执行r2.setManufacturer("GE");时,r2的manufacturer指向新的字符串对象"GE",所以我们看到以上的结果。

好了,问题来了。当存在继承关系时,父类没有公开的clone方法,而子类需要深层拷贝时,子类的clone方法是否安全呢?

显而易见,如果子类的clone方法依赖父类的clone就会出问题,除非保证父类公开了clone方法并正确的实现了它,否则就会出现示例的情况;

此外,当我们为Car类实现clone方法时,是否要依赖Engine类提供公开的且正确的clone呢?如果Engine类是一个final类呢?对于前一个问题,如果依赖,那么Engine不能是一个final类,因为如果是final类,就没法提供公开的clone方法(无法实现Cloneable接口);如果Engine类是final,则Car类的clone就无法依赖Engine的clone,原因同上。

如果不为Car类提供行为良好的clone,那么子类RaceCar就不能依赖于父类的clone,而要自己实现行为正确的clone,就本例而言,可以这样:

@Override

protected Object clone() throws CloneNotSupportedException {

RaceCar racecar = (RaceCar) super.clone();

racecar.setEngine(new Engine(getEngine().getType(), getEngine().getPower()));

racecar.speeder = speeder;

return racecar;

}

输出结果

R1 Engine Type: RR-X7

R2 Engine Type: RR-X8

R1 Manufacturer: BMW

R2 Manufacturer: GE

总结如下:

java中的clone约束是很弱的,因为没有规定一定要实现,但全部都实现又没有必要,因此在使用clone方法进行深层复制时,应该慎重,尤其当存在继承关系时。一个不错的做法是,先调用super.clone,然后对结果对象(super.clone返回对象)的所有域重新赋值(内容为原对象副本),像这样:

@Override

protected Object clone() throws CloneNotSupportedException {

RaceCar racecar = (RaceCar) super.clone();

racecar.setType(getType());

racecar.setManufacturer(getManufacturer());

racecar.setEngine(new Engine(getEngine().getType(), getEngine().getPower()));

racecar.speeder = speeder;

return racecar;

}

3.替代方案

(1)提供一个拷贝构造函数(如果你用过C++就不会陌生)

public RaceCar(RaceCar raceCar);

(2)提供一个静态工厂方法,当然名字可以改变,比如deepCopy等

public RaceCar newInstance(RaceCar raceCar);

(3)使用序列化

如何实现此处不再赘述,资料有很多,本文提供的连接也提及,可以参考。

ps:示例代码的clone是protected的,因为文件都放在同一包中,所以访问没问题,实际中也许要改为public

分享到:

18e900b8666ce6f233d25ec02f95ee59.png

72dd548719f0ace4d5f9bca64e1d7715.png

2011-09-15 15:16

浏览 6262

评论

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值