代码改变世界

谨慎重载clone方法

2017-03-19 16:42  ttylinux  阅读(791)  评论(0编辑  收藏  举报
本文涉及到的概念
1.浅拷贝和深拷贝
2..clone方法的作用和使用方式
3.拷贝构造器和拷贝工厂
 
1.浅拷贝和深拷贝
浅拷贝
一个类实现Cloneable接口,然后,该类的实例调用clone方法,返回一个新的实例。
新的实例与原来的实例是不同的对象。
新的实例的各个非引用类型的成员变量值与原来的实例的成员变量值相同。
对于引用类型的成员变量,新实例的成员变量和旧实例的成员变量,都是指向相同的对象,引用值相同,这种称作浅拷贝(shallow copying)
public class Car implements Cloneable {

private Object containedObj1 = new Object();
private Object containedObj2 = new Object();

public Object getObj1() {
return containedObj1;
}

public Object getObj2() {
return containedObj2;
}

@Override
protected Object clone() throws CloneNotSupportedException {

return (Car) super.clone();

}

public Car() {

}

public static void main(String[] args) {

try {
Car obj1 = new Car();
Car obj2 = (Car) obj1.clone();

System.out.println("obj1 and obj2 are same:" + (obj1 == obj2));
System.out.println("obj1.containedObj1 and obj2.containedObj1 are same:" + (obj1.getObj1() == obj2.getObj1()));
System.out.println("obj1.containedObj2 and obj2.containedObj2 are same:" + (obj1.getObj2() == obj2.getObj2()));
System.out.println("obj1.str and obj2.str are same:" +(obj2.getString() == obj1.getString()));
System.out.println("obj1.data:" + obj1.getData()+"; obj2.data:" + obj2.getData());
System.out.println("obj1.dataf:" + obj1.getDataf()+"; obj2.dataf:" + obj2.getDataf());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}

}
}
输出结果:
obj1 and obj2 are same:false
obj1.containedObj1 and obj2.containedObj1 are same:true
obj1.containedObj2 and obj2.containedObj2 are same:true
obj1.str and obj2.str are same:true
obj1.data:1024; obj2.data:1024
obj1.dataf:1024.1024; obj2.dataf:1024.1024
 
深拷贝(deep copying)
使用拷贝构造方法来实现深拷贝
深拷贝与浅拷贝的区别是,对于引用类型的成员变量,经过深拷贝之后,两者的值不相等,它们不是指向相同的对象,所以引用值不同。
深拷贝就是,对于非引用类型的成员变量复制值,对于引用类型的成员变量,创建新的对象,将新对象的地址复制给引用类型的成员变量
 
下面并没有使用clone方法来实现深拷贝,阅读相关资料后,发现使用clone方法来实现深拷贝,限制和问题太多了。
public class Car2 {
private Object containedObj1 = new Object();
private Object containedObj2 = new Object();

private String str = "oneStr";

private int data = 1024;
private float dataf = 1024.1024f;

public Car2() {

}

public Car2(Car2 car) {

this.str = new String(car.getString().toString());
this.data = car.getData();
this.dataf = car.getDataf();

}

private String getString() {
return str;
}

public int getData() {
return data;
}

public float getDataf() {
return dataf;
}

public Object getObj1() {
return containedObj1;
}

public Object getObj2() {
return containedObj2;
}

public static void main(String[] args) {

Car2 obj1 = new Car2();
Car2 obj2 = new Car2(obj1);

System.out.println("obj1 and obj2 are same:" + (obj1 == obj2));
System.out.println("obj1.containedObj1 and obj2.containedObj1 are same:" + (obj1.getObj1() == obj2.getObj1()));
System.out.println("obj1.containedObj2 and obj2.containedObj2 are same:" + (obj1.getObj2() == obj2.getObj2()));
System.out.println("obj1.str and obj2.str are same:" +(obj2.getString() == obj1.getString()));
System.out.println("obj1.data:" + obj1.getData()+"; obj2.data:" + obj2.getData());
System.out.println("obj1.dataf:" + obj1.getDataf()+"; obj2.dataf:" + obj2.getDataf());

}
}
obj1 and obj2 are same:false
obj1.containedObj1 and obj2.containedObj1 are same:false
obj1.containedObj2 and obj2.containedObj2 are same:false
obj1.str and obj2.str are same:false
obj1.data:1024; obj2.data:1024
obj1.dataf:1024.1024; obj2.dataf:1024.1024
 
2.clone方法的作用和使用方式
使用clone方法来实现深拷贝,就是一个坑。使用clone来实现浅拷贝,是不错的选择,但是不适合用来实现深拷贝。
 
使用clone实现深拷贝
Body类含有引用类型成员变量head,Head类含有引用类型成员变量
现在要对Body类实现深度拷贝,使用clone方法来实现,方式是,对于要被深度拷贝的类实现Cloneable接口,重载clone方法,返回类型是当前类。
Body类要实现深拷贝,实现Cloneable接口,重载clone方法
Body newBody = (Body)super.clone(),调用Body类的父类,实现Body各个非引用类型的变量的浅拷贝。
接着,要对Body的引用类型成员变量head进行深拷贝,newBody.head = (Head) head.clone(); 执行之后,可以实现Head类的浅拷贝,而对Body来说,head变量就是深拷贝了,新的Body.head的引用值指向新的Head对象。
 
也就是,要对某个类实现深拷贝,那么,就必须保证类中的引用类型成员变量,这些类它们实现了Cloneable接口,然后,我们再在当前类中实现Cloneable接口,对每一个引用类型的成员变量调用clone方法。
 
这还只是能确保当前成员变量被深度拷贝了,不能保证当前成员变量中的引用类型成员变量被深度拷贝。
@Override
protected Object clone() throws CloneNotSupportedException {
Body newBody = (Body) super.clone();
newBody.head = (Head) head.clone();
return newBody;
}
public class Body implements Cloneable {
public Head head;

public Body() {
}

public Body(Head head) {
this.head = head;
}

@Override
protected Object clone() throws CloneNotSupportedException {
Body newBody = (Body) super.clone();
newBody.head = (Head) head.clone();
return newBody;
}

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

Body body = new Body(new Head());

Body body1 = (Body) body.clone();

System.out.println("body == body1 : " + (body == body1));

System.out.println("body.head == body1.head : " + (body.head == body1.head));

}
}

class Head implements Cloneable/* implements Cloneable */ {
public Face face;

public Head() {
}

public Head(Face face) {
this.face = face;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}

}

class Face {

}
输出结果:
body == body1 : false
body.head == body1.head : false
 
只是保证对当前的引用类型的变量进行深度拷贝
Body实现了Cloneable,Head实现了Cloneable;Head中的Face类型没有实现Cloneable;
 
对Body进行深拷贝,Body中的Head类型的成员变量被深度拷贝了,但是Head里面的Face类型的成员变量进行的是浅拷贝。
public class Body2 implements Cloneable {
public Head head;

public Body2() {
}

public Body2(Head head) {
this.head = head;
}

@Override
protected Object clone() throws CloneNotSupportedException {
Body2 newBody = (Body2) super.clone();
newBody.head = (Head) head.clone();
return newBody;
}

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

Body2 body = new Body2(new Head(new Face()));

Body2 body1 = (Body2) body.clone();

System.out.println("body == body1 : " + (body == body1));

System.out.println("body.head == body1.head : " + (body.head == body1.head));

System.out.println("body.head.face == body1.head.face : " + (body.head.face == body1.head.face));

}
}

class Head implements Cloneable/* implements Cloneable */ {
public Face face;

public Head() {
}

public Head(Face face) {
this.face = face;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}

}

class Face {

}
输出结果:
body == body1 : false
body.head == body1.head : false
body.head.face == body1.head.face : true
 
 
3.拷贝构造器和拷贝工厂
通过上面的例子,我们可以知道,如果只是实现浅拷贝,可以使用clone方法(对要被浅拷贝的类,该类要实现Cloneable接口)。如果使用Clone方法来实现深拷贝,那将是一个坑。
<<effective Java>>中推荐使用拷贝构造器和拷贝工厂的方式来实现对象的拷贝。
 
下面的代码,使用了拷贝构造器和拷贝工厂来实现对象的拷贝
创建一个对象m101,然后,使用m101作为参数,拷贝它,使用拷贝构造器来实现,得到一个新的对象m101CopyOne;
使用m101CopyOne对象作为参数,通过拷贝工厂方法,活动一个新的对象m101CopyTwo
public final class Galaxy {

  /**
  * Regular constructor.
  */
  public Galaxy(double aMass, String aName) {
    fMass = aMass;
    fName = aName;
  }

  /**
  * Copy constructor.
  */
  public Galaxy(Galaxy aGalaxy) {
    this(aGalaxy.getMass(), aGalaxy.getName());
    //no defensive copies are created here, since
    //there are no mutable object fields (String is immutable)
  }

  /**
  * Alternative style for a copy constructor, using a static newInstance
  * method.
  */
  public static Galaxy newInstance(Galaxy aGalaxy) {
    return new Galaxy(aGalaxy.getMass(), aGalaxy.getName());
  }

  public double getMass() {
    return fMass;
  }

  /**
  * This is the only method which changes the state of a Galaxy
  * object. If this method were removed, then a copy constructor
  * would not be provided either, since immutable objects do not
  * need a copy constructor.
  */
  public void setMass(double aMass){
    fMass = aMass;
  }

  public String getName() {
    return fName;
  }

  // PRIVATE
  private double fMass;
  private final String fName;

  /** Test harness. */
  public static void main (String... aArguments){
    Galaxy m101 = new Galaxy(15.0, "M101");

    Galaxy m101CopyOne = new Galaxy(m101);
    m101CopyOne.setMass(25.0);
    System.out.println("M101 mass: " + m101.getMass());
    System.out.println("M101Copy mass: " + m101CopyOne.getMass());

    Galaxy m101CopyTwo = Galaxy.newInstance(m101);
    m101CopyTwo.setMass(35.0);
    System.out.println("M101 mass: " + m101.getMass());
    System.out.println("M101CopyTwo mass: " + m101CopyTwo.getMass());
  }
}
输出结果:
M101 mass: 15.0
M101Copy mass: 25.0
M101 mass: 15.0
M101CopyTwo mass: 35.0

 

总结:
拷贝一个对象,避免使用Clone的方式,使用拷贝构造器和拷贝工厂的方法来获得一个新的对象。同时也避免使用序列化反序列化的方式来实现对象的拷贝。
Clone的方式,只适合用来实现浅拷贝。