Java 对象的浅拷贝与深拷贝

Java 对象的深度克隆

Java 对象的引用与克隆

在Java中,常会用到下面的代码逻辑:

class Person implements Cloneable{
        String name;
        int age;
        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }

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

Person p = new Person("zhangsan",20);  
Person p1 = p;  
  
System.out.println("p == p1:" + (p1 == p));  

Person p1=p是创建了一个新对象p1还是p1只是对象p的一个引用?

看打印的结果:p == p1: true

所以p1和p的地址是相同的,p1只是p的一个引用。内存引用示意图如下:


那么如何创建一个和p相同的新对象而又不是其的引用呢?可以使用Java的克隆机制。

Person p = new Person("zhangsan",20);  
Person p1 = (Person) p.clone();  

System.out.println(p);  
System.out.println(p1);
System.out.println(p1 == p);

看打印的结果:

com.xxx.simulate.city.util.UtilTest$Person@1d057a39
com.xxx.simulate.city.util.UtilTest$Person@26be92ad
false

使用clone()方法克隆的一个对象p,不再是p1的引用而是创建了一个新的对象。


浅克隆与深度克隆(浅拷贝与深度拷贝)

Java对象的浅拷贝

上面的示例代码中,Person中有两个成员变量,分别是name和age, name是String类型, age是int类型。

由于age是基本数据类型, 那么对它的拷贝没有什么疑议,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的, 它只是一个引用, 指向一个真正的String对象,那么对它的拷贝有两种方式: 直接将源对象中的name的引用值拷贝给新对象的name字段, 或者是根据原Person对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的name字段。这两种拷贝方式分别叫做浅拷贝和深拷贝。深拷贝和浅拷贝的内存示意图如下所示:



代码验证:

Person p = new Person("zhangsan",20);  
Person p1 = (Person) p.clone();  
  
String result = p.getName() == p1.getName()   
        ? "clone是浅拷贝的" : "clone是深拷贝的";  
  
System.out.println(result);  
看打印结果:clone是浅拷贝的。
所以, clone方法执行的是浅拷贝!

Java对象的深度拷贝

实现Java对象的深度拷贝,可以通过覆盖Object中的clone方法来实现。

现在为了要在clone对象时进行深拷贝, 那么就要实现Clonable接口,覆盖并实现clone方法,除了调用父类中的clone方法, 还要将该类中的引用变量也clone出来。

上代码:

static class Body implements Cloneable{  
    public Head head;  
      
    public Body() {}  
  
    public Body(Head head) {this.head = head;}  
  
    @Override  
    protected Object clone() throws CloneNotSupportedException {  
        return super.clone();  
    }  
      
}
 
static class Head /*implements Cloneable*/{  
    public  Face face;  
      
    public Head() {}  
    public Head(Face face){this.face = face;}  
      
}
   
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));   
}  

打印结果:

body == body1 : false

body.head == body1.head : true

在以上代码中, 有两个主要的类, 分别为Body和Face, 在Body类中, 组合了一个Face对象。当对Body对象进行clone时, 它组合的Face对象只进行了浅拷贝。

如果要使Body对象在clone时进行深拷贝, 那么就要在Body的clone方法中,将源对象引用的Head对象也clone一份。

static 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;
	}
	
}

static class Head implements Cloneable{
	public  Face face;
	
	public Head() {}
	public Head(Face face){this.face = face;}
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
} 

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));
}
打印结果为:
body == body1 : false

body.head == body1.head : false

真的是深拷贝吗?

按照上面的结论, Body类组合了Head类, 而Head类组合了Face类,要想深拷贝Body类,必须在Body类的clone方法中将Head类也要拷贝一份,但是在拷贝Head类时,默认执行的是浅拷贝,也就是说Head中组合的Face对象并不会被拷贝。

对Body对象来说,这算是深拷贝吗?其实应该算是深拷贝,因为对Body对象内所引用的其他对象(目前只有Head)都进行了拷贝,也就是说两个独立的Body对象内的head引用已经指向了独立的两个Head对象。但是,这对于两个Head对象来说,他们指向了同一个Face对象,这就说明,两个Body对象还是有一定的联系,并没有完全的独立。这应该说是一种不彻底的深拷贝。

如何彻底的深拷贝?

对于上面的例子来说,怎样才能保证两个Body对象完全独立呢?只要在拷贝Head对象的时候,也将Face对象拷贝一份就可以了。这需要让Face类也实现Cloneable接口,实现clone方法,并且在在Head对象的clone方法中,拷贝它所引用的Face对象。

依此类推,如果Face对象还引用了其他的对象, 比如说Mouth,如果不经过处理,Body对象拷贝之后还是会通过一级一级的引用,引用到同一个Mouth对象。同理, 如果要让Body在引用链上完全独立, 只能显式的让Mouth对象也被拷贝。

用序列化方式实现复杂对象的深拷贝

对象克隆的应用

创建不可变对象



猜你喜欢

转载自blog.csdn.net/oitebody/article/details/80787092