`
米奈希尔
  • 浏览: 267081 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

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;
	}
	
	@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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics