首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

Java对象复制-慎用clone

2012-10-16 
Java对象复制--慎用clone什么是拷贝、影子拷贝、深度拷贝,不是本文要讨论的。如需了解,以下两个连接还是不错

Java对象复制--慎用clone

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

http://liran-email.iteye.com/blog/550249

http://www.ibm.com/developerworks/cn/java/l-jpointer/index.html

?

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;}@Overrideprotected 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-X8R2 Engine Type: RR-X8R1 Manufacturer: BMWR2 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,就本例而言,可以这样:

@Overrideprotected 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-X7R2 Engine Type: RR-X8R1 Manufacturer: BMWR2 Manufacturer: GE

总结如下:

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

@Overrideprotected 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

热点排行