两种 Java 深层复制方式
为什么需要深层复制
Object 的 clone() 方法是浅层复制(但是 native 很高效)。
另外,Java 提供了数组和集合的复制方法,分别是 Arrays.copy() 和 Collections.copy() 方法。
前者实际上使用了 System.arraycopy() 方法,两者其实也是浅层复制,过程类似于下面的 for 循环:
for(int i=0; i<len; i++){
dest[i] = src[i];
}
所以当数组或集合中元素是对象时,只是做了引用的复制,指向的还是堆中同一个对象。
一般有两种深层复制方案
1)实现 Cloneable 接口
包装 Object.clone() ,根据属性类型深度 clone。
这种方法,使用了 Object.clone() ,优点是 native 方法性能好,缺点是实现太繁琐。
/**
* 0 实现 Cloneable 接口
* 1 包装 super.clone(),提供 public 方法
* 2 默认的是浅层复制
* @author Super
*
*/
public class shallowCloneTest {
public static void main(String[] args) {
Resource0 r0 = new Resource0(0, "资源1号");
Resource0 r1 = new Resource0(1, "内部资源");
r0.setInnerResource(r1);
//验证克隆
Resource0 r2 = r0.shallowClone();
System.out.println(r0);
System.out.println(r2);
System.out.println(r1==r2); //false
//验证浅度克隆
r2.getInnerResource().setId(7);
System.out.println(r1); //受影响了
}
}
/**
* 深层复制方案一:包装 clone,引用变量继续 clone
* @author Super
*
*/
public class DeepCloneTest1 {
public static void main(String[] args) {
Resource0 r0 = new Resource0(0, "资源1号");
Resource0 r1 = new Resource0(1, "内部资源");
r0.setInnerResource(r1);
//验证克隆
Resource0 r2 = r0.deepClone();
System.out.println(r0);
System.out.println(r2);
System.out.println(r1==r2); //false
//验证深度度克隆
r2.getInnerResource().setId(7);
System.out.println(r1); //不受影响
}
}
public class Resource0 extends BaseVo implements Cloneable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Resource0 innerResource;
public Resource0(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Resource0 getInnerResource() {
return innerResource;
}
public void setInnerResource(Resource0 innerResource) {
this.innerResource = innerResource;
}
/**
* 浅层复制
* @return
*/
public Resource0 shallowClone(){
Resource0 r = null;
try {
r = (Resource0)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return r;
}
/**
* 深层复制
* @return
*/
public Resource0 deepClone(){
Resource0 r = null;
try {
r = (Resource0)super.clone();
r.innerResource = (Resource0) innerResource.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return r;
}
@Override
public String toString() {
return "Resource0 [id=" + id + ", name=" + name + ", innerResource="
+ innerResource + "]";
}
}
View Code
2)对象序列化输入输出
这种方法,强大且简单,先写到内存,再读出来。
PS:单例对象最好也关注下是否有被序列化复制的风险。
/**
* 深层复制方案2:输入输出序列化
* @author Super
*
*/
public class DeepCloneTest2 {
public static void main(String[] args) {
Resource0 r0 = new Resource0(0, "资源1号");
Resource0 r1 = new Resource0(1, "内部资源");
r0.setInnerResource(r1);
//验证克隆
Resource0 r2 = (Resource0) IOUtil.deepClone(r0);
System.out.println(r0);
System.out.println(r2);
System.out.println(r1==r2); //false
//验证深度度克隆
r2.getInnerResource().setId(7);
System.out.println(r1); //不受影响
}
}
/**
* 深层复制序列化 vo
* @param src
* @return dest
* @throws IOException
* @throws ClassNotFoundException
*/
public static BaseVo deepClone(BaseVo src) {
ByteArrayOutputStream bo = null;
ObjectOutputStream out = null;
ObjectInputStream in = null;
BaseVo dest = null;
try{
try{
//对象写入内存
bo = new ByteArrayOutputStream();
out = new ObjectOutputStream(bo);
out.writeObject(src);
//从内存中读回来
in = new ObjectInputStream(new ByteArrayInputStream(bo.toByteArray()));
dest = (BaseVo) in.readObject();
}finally{
//使用 finally 关闭资源
if(in!=null){
in.close();
}
if(out!=null){
out.close();
}
if(bo!=null){
bo.close();
}
}
//使用 catch 块统一捕捉资源
} catch(IOException | ClassNotFoundException ex){
ex.printStackTrace();
}
return dest;
}