什么是拷贝、影子拷贝、深度拷贝,不是本文要讨论的。如需了解,以下两个连接还是不错滴。
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
分享到:
相关推荐
**【推荐】慎用`Object`的`clone`方法来拷贝对象** - **原因**:默认情况下,`clone`方法执行的是浅拷贝。若要实现深拷贝,需要重写`clone`方法并实现深度拷贝。 - **实践建议**:在需要深拷贝的情况下,考虑使用...
1.3 慎用异常 异常处理不应作为常规控制流程,因为创建异常对象和填充堆栈跟踪信息会消耗性能。只有在处理错误时才应使用异常。 1.4 不要重复初始化变量 Java会自动初始化变量,重复初始化只会增加不必要的计算。在...
3. **慎用异常** 异常处理应该仅用于错误情况,而不是作为常规控制流程。创建异常对象和填充堆栈轨迹会导致性能下降,因此应谨慎使用。 4. **不要重复初始化变量** Java会自动初始化变量,所以在构造函数中不必要...
慎用全局变量 全局变量虽方便,但容易引发状态混乱和多线程安全问题。应优先考虑局部变量和参数传递,以保持函数的纯净性和可测试性。 #### 6. 优化循环结构 避免在循环中进行不必要的计算或查询,这会显著降低...
网管教程 从入门到精通软件篇 ★一。★详细的xp修复控制台命令...JAVA:Java源文件 JFF,JFIF,JIF:JPEG文件 JPE,JPEG,JPG:JPEG图形文件 JS:javascript源文件 JSP:HTML网页,其中包含有对一个Java servlet...