Java对象克隆

Java对象克隆

为什么要重写clone来克隆对象?

  1. 需要新建一个对象来保存当前对象的状态。
  2. 因为Java的赋值语句对于抽象对象来说是传递引用,所以复制对象需要特殊的实现
  3. native方法,运行速度快

clone()

Object有两个protected方法,其中一个为clone方法

1
protected native Object clone() throws CloneNotSupportedException;

它是native方法,就是非Java语言实现的代码,供Java程序调用。Java语言是运行于虚拟机上的,想要访问底层于操作系统相关的资源就需要底层语言来实现

  • 第一次声明保证克隆对象有单独的内存地址分配
  • 第二次声明表示原始对象和克隆对象有相同的类型
  • 第三次声明表示原始和克隆的对象是平等的equals()方法使用

每个抽象类都继承了clone方法,但因为是protected所以无法在外部包使用,所以需要重写clone方法。


两种克隆方法

两种克隆方法:

  • 浅克隆(ShallowClone)
  • 深克隆(DeepClone)

两者的主要区别在是否支持成员变量是引用类型的复制。

在克隆一个对象时,如果对象的域都是基本类型或数值,那么只需要一个个域的拷贝就够了,但当对象还包含对子对象的引用时,只拷贝域的话只能得到对象的引用,所以在克隆后原本和副本都会指向同一个子对象,这就是浅克隆(ShallowClone);若子对象也会拷贝创建一份副本,克隆后原本和副本都各自指向自己的子对象,则就是深克隆(DeepClone)。

因此,当子对象不属于一个final修饰的不可变类时(如String等),浅克隆无法保证安全性。所以需要深克隆来保证数据安全。

浅克隆

实现浅克隆步骤:

  1. 被复制的类要先实现Cloneable接口,标记该类支持克隆,否则会抛出异常CloneNotSupportedException
  2. 重写clone()方法,访问修饰符public。方法中调用super.clone()方法得到需要的复制对象。
1
2
3
4
5
6
7
8
9
10
@Override
public Object clone(){
ShallowClone clone = null;
try{
clone = (ShallowClone)super.clone();
}catch (CloneNotSupportedException ex){
ex.printStackTrace();
}
return clone;
}
1
2
3
4
5
6
7
8
9
10
11
@Override
public Object clone(){
DeepClone clone = null;
try{
clone = (DeepClone)super.clone(); //浅复制
clone.adress = (Adress) adress.clone(); //深复制
}catch (CloneNotSupportedException ex){
ex.printStackTrace();
}
return clone;
}

多层克隆问题

引用类型里面包含引用类型,此时使用覆盖clone方法会比较麻烦,而深克隆除了覆盖clone方法也可以通过对象序列化来实现:

序列化就是将对象写入流中,是对原对象的拷贝,原对象仍然在内存中,需要对象类实现Serializable接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public SerializableClone ownClone(){//深度复制方法,需要对象及对象所有的对象属性都实现序列化
SerializableClone clone = null;
try{
//将该对象序列化成流,是对对象的拷贝,原对象仍存活在JVM中
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
//将流序列化为对象
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
clone = (SerializableClone)objectInputStream.readObject();
}catch (IOException | ClassNotFoundException ex){
ex.printStackTrace();
}
return clone;
}

但序列化的性能相比复制数据域来说是要慢很多的,可以根据使用场景和需求来选择:比如对性能要求很高的地方就直接进行深复制,不太要求性能就可以考虑序列化,或用一些序列化框架,或是通过JSON进行序列化。


参考博客和文章书籍等:

《Java核心技术 卷Ⅰ》

https://www.cnblogs.com/Qian123/p/5710533.html

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容