java中的深拷贝与浅拷贝

一 序

  最初这篇打算放在arraylist里面整理,因为arraylist也是实现了clone接口。想想还是摘出来吧

public interface Cloneable {
}
关于clone,就是复制。它允许在堆中克隆出一块和原对象一样的对象,并将这个对象的地址赋予新的引用。 

Java 中 一个类要实现clone功能 必须实现 Cloneable接口,否则在调用 clone() 时会报 CloneNotSupportedException 异常。

 java.lang.CloneNotSupportedException: com.daojia.collect.Person
	at java.lang.Object.clone(Native Method)

Java中所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone(),这个方法将返回Object对象的一个拷贝。 

    protected native Object clone() throws CloneNotSupportedException;

Object类的clone()方法是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为 什么要用Object中clone()方法而不是先new一个对象,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能,但效率较低。

日常编码里面使用的不多,但是对于理解Java的值传递,引用传递方式有帮助。

二 创建Java对象方式

   1 通过 new 关键字
  这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象。比如 Object obj = new Object();

 2 通过 反射:如Class 类的 newInstance() 方法

 这种默认是调用类的无参构造方法创建对象。比如 Person p2 = (Person) Class.forName("com.daojia.collect.Person").newInstance();

   3 通过clone:就是本篇要整理的。

    如:  Person p2 = (Person) p1.clone();

三 浅拷贝

 浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。clone就是浅拷贝。

  补充知识:在 Java 中数据类型可以分为两大类:基本类型和引用类型。
 基本类型也称为值类型,分别是字符类型 char,布尔类型 boolean以及数值类型 byte、short、int、long、float、double。
 引用类型则包括类、接口、数组、枚举等。
    Java 将内存空间分为堆和栈。基本类型直接在栈中存储数值,而引用类型是将引用放在栈中,实际存储的值是放在堆中,通过栈中的引用指向堆中存放的数据。

下面看个例子:调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法。

public class Person implements Cloneable{
	
    public String name;
    public int age;
    public Address address;
    public Person() {}
    
    public Person(String name,int age){
        this.name = name;
        this.age = age;
        this.address = new Address();
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    public void setAddress(String provices,String city ){
        address.setAddress(provices, city);
    }
    public void display(String name){
        System.out.println(name+":"+"name=" + name + ", age=" + age +","+ address);
    }
   
        public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}
}
public class Address {
    private String provices;
    private String city;
    public void setAddress(String provices,String city){
        this.provices = provices;
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [provices=" + provices + ", city=" + city + "]";
    }
    
}
public class TestCopy {

	public  static void main(String[] args) throws Exception{
	    Person p1 = new Person("zhangsan",21);
	    p1.setAddress("X省", "X市");	  
	    Person p2 = (Person) p1.clone();
	    Person p3 = p1;
	    System.out.println("p1:"+p1);	    
	    System.out.println("p2:"+p2);
	    System.out.println("p3:"+p3);

	    p1.display("p1");
	    p2.display("p2");
	    p3.display("p3");
	  
	    p1.setName("lisi");
	    p3.setAge(19);
	    p2.setAddress("Y省", "Y市");
	    System.out.println("将复制之后的对象地址修改:");
	    p1.display("p1");
	    p2.display("p2");
	    p3.display("p3");
	}	
}
输出:
p1:com.daojia.collect.Person@15db9742
p2:com.daojia.collect.Person@6d06d69c
p3:com.daojia.collect.Person@15db9742
p1:name=p1, age=21,Address [provices=X省, city=X市]
p2:name=p2, age=21,Address [provices=X省, city=X市]
p3:name=p3, age=21,Address [provices=X省, city=X市]
将复制之后的对象地址修改:
p1:name=p1, age=19,Address [provices=Y省, city=Y市]
p2:name=p2, age=21,Address [provices=Y省, city=Y市]
p3:name=p3, age=19,Address [provices=Y省, city=Y市]
    首先看原始类 Person 实现 Cloneable 接口,并且覆写 clone 方法,它还有三个属性,一个引用类型 String定义的 name,一个基本类型 int定义的 age,还有一个引用类型 Address ,这是一个自定义类,这个类也包含两个属性 provices 和 city 。
  接着看测试内容,首先我们创建一个Person 类的对象 p1,其name 为zhangsan,age为21,地址类 Address 两个属性为 X省和X市。接着我们调用 clone() 方法复制另一个对象 p2,p1复制了一个对象p3,接着打印这几个对象的内容。
  p1:com.daojia.collect.Person@15db9742
p2:com.daojia.collect.Person@6d06d69c

p3:com.daojia.collect.Person@15db9742

可见p1,p3都是引用了同一个对象,而p2是不同的对象,说明clone是生成新对象而不是一个引用; 

接下来看打印出的对象内容,原对象 p1 和克隆出来的对象 p2 内容完全相同。说明clone与new的不同之处,clone的对象已经包含原来对象的信息,不是new的默认信息。

再看修改引用类型的address值。我们只是更改了克隆对象 p2 的属性 Address 为Y省Y市,却发现p1,p2 的Address 属性都被修改了。就是说对象 Person 的属性 Address,经过 clone 之后,其实只是复制了其引用,他们指向的还是同一块堆内存空间,当修改其中一个对象的属性 Address,另一个也会跟着变化。



四 深拷贝

  深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。

实现方式一:让每个引用类型属性内部都重写clone() 方法

 如Address ,方式参见person,不写了。举的例子Person有引用Address,如果 Address 类也存在一个引用类型,那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,太麻烦了,不推荐。

实现方式二:利用序列化Serializable

序列化通常用作内存中的对象状态保存到一个文件中或者数据库中时候;或者对象要在网络中传输时候。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

public class Person implements Serializable,Cloneable{
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	public String name;
    public int age;
    public Address address;
    public Person() {}
    
    public Person(String name,int age){
        this.name = name;
        this.age = age;
        this.address = new Address();
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    public void setAddress(String provices,String city ){
        address.setAddress(provices, city);
    }
    public void display(String name){
        System.out.println(name+":"+"name=" + name + ", age=" + age +","+ address);
    }

   
  public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	//深度拷贝
  	public Object deepClone() throws Exception{
  	    // 序列化
  	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
  	    ObjectOutputStream oos = new ObjectOutputStream(bos);

  	    oos.writeObject(this);

  	    // 反序列化
  	    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  	    ObjectInputStream ois = new ObjectInputStream(bis);

  	    return ois.readObject();
  	}
}
public class Address implements Serializable{
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String provices;
    private String city;
    public void setAddress(String provices,String city){
        this.provices = provices;
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [provices=" + provices + ", city=" + city + "]";
    }
    
}
public class TestCopy {

	public  static void main(String[] args) throws Exception{
	    Person p1 = new Person("zhangsan",21);
	    p1.setAddress("X省", "X市");	  
	    Person p2 = (Person) p1.clone();
	    Person p3 = (Person) p1.deepClone();
	    System.out.println("p1:"+p1);
	    System.out.println("p1.getName:"+p1.getName());
	    
	    System.out.println("p2:"+p2);
	    System.out.println("p2.getName:"+p2.getName());
	    System.out.println("p3:"+p3);
	    System.out.println("p3.getName:"+p3.getName());
	    p1.display("p1");
	    p2.display("p2");
	    p3.display("p3");
	  
	    p1.setName("lisi");
	    p2.setAge(19);
	    p3.setAddress("Y省", "Y市");
	    System.out.println("将复制之后的对象地址修改:");
	    p1.display("p1");
	    p2.display("p2");
	    p3.display("p3");
	}
	
	
}

输出:

p1:com.daojia.collect.Person@3d4eac69
p1.getName:zhangsan
p2:com.daojia.collect.Person@214c265e
p2.getName:zhangsan
p3:com.daojia.collect.Person@448139f0
p3.getName:zhangsan
p1:name=p1, age=21,Address [provices=X省, city=X市]
p2:name=p2, age=21,Address [provices=X省, city=X市]
p3:name=p3, age=21,Address [provices=X省, city=X市]
将复制之后的对象地址修改:
p1:name=p1, age=21,Address [provices=X省, city=X市]
p2:name=p2, age=19,Address [provices=X省, city=X市]
p3:name=p3, age=21,Address [provices=Y省, city=Y市]


 参考:

http://www.cnblogs.com/ysocean/p/8482979.html

猜你喜欢

转载自blog.csdn.net/bohu83/article/details/80856097