1.首先想到的是用"="
Object a = new Object();
Object b = a;
除了初学者,稍微懂些java语言的人都会注意到引用数据类型的问题.
b并不是被创建的和a相同的对象,b就是a,b只是获得a指向的对象的引用.
2.其次就是最朴素的,怎么创建的a就怎么创建b,重新走一遍获得a的流程
这算是最基础的方法了,刚学数组的时候会做很多复制数组的练习,这些练习就是这种最简单的方法.
针对于数组有System.arraycopy(Object src ,int srcPos ,Object dest ,int destPos , int length),
和Arrays.copyOf().
在这里存在一个问题,即如果数组里的元素还是引用数据类型,则最后复制的也并不是一个全新的数组对象,而只是一个简单的新的"数组"对象.
3.对于普通的对象的话,有一个api是Object.clone()
这个类的说明是Creates and returns a copy of this object.创建并返回一个此对象的复制.
/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
第一次声明,保证克隆对象将有单独的内存地址分配。
第二次声明,表明原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
第三此声明,表明原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。
对于2.中提到的重新创建一遍对象存在许多问题,因为原始对象可能在初始化后经历了许多逻辑,如果再走一遍跟提高复用性背道而驰,
所以clone()应该算是最优的解决方案.它即可以复制一个刚初始化的对象,也可以复制一个已经修改过属性的对象.
而对于克隆,针对2.中最后提到的问题,分为浅克隆(ShallowClone)与深克隆(DeepClone),他们的区别就在于是否支持引用数据类型的成员变量的复制
浅克隆的步骤:
1.被复制的类需要实现Cloneable接口
如果不实现此接口而调用clone()方法会抛出CloneNotSupportedException异常.
此接口为标记接口,不含任何方法.
2.重写clone()方法,访问权限设为public,方法中调用super,clone()方法得到需要复制的对象.
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
深克隆的步骤:
1.被复制的类,和类中所有的引用数据类型,都要实现Cloneable接口
2.被复制的类,和类中所有的引用数据类型,都要重写clone()方法.
class Address implements Cloneable {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //浅复制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone(); //深度复制
return stu;
}
}
可参考java.util.Date;的clone()
/**
* Return a copy of this object.
*/
public Object clone() {
Date d = null;
try {
d = (Date)super.clone();
if (cdate != null) {
d.cdate = (BaseCalendar.Date) cdate.clone();
}
} catch (CloneNotSupportedException e) {} // Won't happen
return d;
}
4.而在实际问题中,3.中提到的深克隆也会面临一个问题--那就是对象中引用数据类型过多过深,引用数据类型属性还有引用数据类型的属性,使用3.中的深克隆还是很麻烦.
其实深克隆还有另外一种实现方式,就是对象的序列化(Serialization)与反序列化(Deserialization).
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在内存中.通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用数据类型的成员变量.因此通过序列化将对象写到一个流中,再通过反序列化将其读取出来,就可以实现深克隆.
虽然Java的序列化非常简单、强大,但是要用好,还有很多地方需要注意。
比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。 你可以通过添加serialVersionUID属性来解决这个问题。
如果你的类是个单例(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。
public class Attribute {
private String no;
}
public class Product {
private String name;
private Attribute attribute;
public Product clone() {
ByteArrayOutputStream byteOut = null;
ObjectOutputStream objOut = null;
ByteArrayInputStream byteIn = null;
ObjectInputStream objIn = null;
try {
byteOut = new ByteArrayOutputStream();
objOut = new ObjectOutputStream(byteOut);
objOut.writeObject(prototype);
byteIn = new ByteArrayInputStream(byteOut.toByteArray());
objIn = new ObjectInputStream(byteIn);
return (ContretePrototype) objIn.readObject();
} catch (IOException e) {
throw new RuntimeException("Clone Object failed in IO.",e);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class not found.",e);
} finally{
try{
byteIn = null;
byteOut = null;
if(objOut != null) objOut.close();
if(objIn != null) objIn.close();
}catch(IOException e){
}
}
}
}
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。