Java对象浅拷贝和深拷贝

Java中的数据类型分为基本数据类型和引用数据类型。

基于基本数据类型和引用类型拷贝时候的数据传递,Java对于拷贝,分为浅拷贝(swallow copy)和深拷贝(deep copy)。

看一个例子,一个Student对象,里面有三个属性,一个String型name,一个Address对象型address,一个集合型hobbies。

public class Student {
	
	// 姓名- 简单属性
	private String name;
	
	// 地址- 对象
	private Address address;
	
	// 爱好- 集合类型
	private List<String> hobbies;
	
	public Student(String name, Address address, List<String> hobbies) {
		this.name = name;
		this.address = address;
		this.hobbies = hobbies;
	}

	...省略get set方法...
	
	public String toString() {
		return "[name:"+name+";address:" + address.toString()+";hobbies:" + hobbies.toString() + "]";
	}
}

附 Address类定义代码,只是为了说明问题,这里Address对象里面仅有一个String型的provice信息。

public class Address {

	// 省
	private String provice;
	
	public Address(String provice) {
		this.provice = provice;
	}

	...省略get set方法...
	
	public String toString() {
		return "[provice:"+ provice+"]";
	}
}

浅拷贝

浅拷贝说明

  1. 对于数据类型是基本数据类型的成员变量,浅拷贝时候会直接复制一份属性值给到新对象。
  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是集合列表、某个类的对象,那么浅拷贝会进行引用传递,就是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

浅拷贝的实现通常有两种

  • 构造方法拷贝 ——使用对象的构造方法,将原对象的各属性值传递过去,获得新的对象。
  • 普通重写clone()方法 ——通过实现clone方法,直接克隆一个新的对象。

基于上面的Student类定义,假如有一个student

		List<String> hobbies = new ArrayList<>();
		hobbies.add("running");
		hobbies.add("skiing");
		
		Address address = new Address("China.Taiwan");
		Student stu = new Student("zhangsan", address, hobbies);

如果想基于stu对象复制一个stuCopy对象,看两种不同实现。

构造方法拷贝

构造方法拷贝,顾名思义,使用对象的构造方法,将原对象的各个属性get到赋给构造方法,构建出一个新的对象。

		Student stuCopy = new Student(stu.getName(), stu.getAddress(), stu.getHobbies());

此时,修改stu的name、address和hobbies,修改后,分别打印stu和stuCopy两个对象

		stu.setName("lisi");;
		hobbies.add("climing");
		address.setProvice("Hainan");
		
		System.out.println(stu);
		System.out.println(stuCopy);

通过控制台输出,可以看到

[name:lisi;address:[provice:Hainan];hobbies:[running, skiing, climing]]
[name:zhangsan;address:[provice:Hainan];hobbies:[running, skiing, climing]]

这里stuCopy除了name是当初拷贝的值,address和hobbies值都被stu后来的set方法给改过了。

这是因为,使用构造方法拷贝对象的时候,基本属性拷贝的是对象的值,对象属性和集合属性拷贝的是对象的引用。拷贝后,如果原对象属性做了修改,新对象的属性值会跟着一起修改。

clone方法拷贝

clone拷贝,须要对象实现cloneable接口,重写clone方法

public class Student implements Cloneable{
	
	// 姓名- 简单属性
	private String name;
	
	// 地址- 对象
	private Address address;
	
	// 爱好- 集合类型
	private List<String> hobbies;
	
	public Student(String name, Address address, List<String> hobbies) {
		this.name = name;
		this.address = address;
		this.hobbies = hobbies;
	}

    ...省略get set方法...
	
	public Student clone() throws CloneNotSupportedException {
		Student stu = (Student) super.clone();
        return stu;
	}
	
	public String toString() {
		return "[name:"+name+";address:" + address.toString()+";hobbies:" + hobbies.toString() + "]";
	}
}

重写后,再测试

	public static void main(String[] args) throws CloneNotSupportedException {
		List<String> hobbies = new ArrayList<>();
		hobbies.add("running");
		hobbies.add("skiing");
		
		Address address = new Address("China.Taiwan");
		Student stu = new Student("zhangsan", address, hobbies);
//		Student stuCopy = new Student(stu.getName(), stu.getAddress(), stu.getHobbies());
		Student stuCopy = stu.clone();
		System.out.println(stuCopy);
		
		stu.setName("lisi");
		hobbies.add("climing");
		address.setProvice("Hainan");
		
		System.out.println(stu);
		System.out.println(stuCopy);
	}

此处,多增加一个初始stuCopy,通过控制台输出,可以看到,使用clone方法拷贝与构造方法拷贝结果相同。

[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing]]
[name:lisi;address:[provice:Hainan];hobbies:[running, skiing, climing]]
[name:zhangsan;address:[provice:Hainan];hobbies:[running, skiing, climing]]

深拷贝

深拷贝说明

浅拷贝时候,对于基本数据类型都已经实现拷贝后修改无影响了,对引用类型的拷贝因为指向同一内存空间而没有实现彻底的隔离。若要实现深拷贝,则主要的给引用类型的属性申请到新的内存空间,让引用类型的拷贝对象不再与原对象指向同一块内存空间,以实现物理上个拷贝隔离。

深拷贝的实现通常有两种方式

  1. clone方法拷贝 ——对引用类型的对象依次实现clone方法
  2. 序列化实现 —— 把原对象写入到一个字节流中,再从字节流中将其读出来,创建一个新的对象

clone方法拷贝

使用clone方法做深拷贝的时候,须对原对象中每一个对象属性都重写clone方法。

因Student对象已经重写了clone方法,这里对Address重写clone方法

public class Address implements Cloneable {

    ...略...
	
	public Address clone() throws CloneNotSupportedException {
		Address addr = (Address) super.clone();
        return addr;
	}
	
	public String toString() {
		return "[provice:"+ provice+"]";
	}
}

同步改造 Student类定义中关于Address属性的获取方式

public class Student implements Cloneable {

	...略...
	
	public Student clone() throws CloneNotSupportedException {
		Student stu = (Student) super.clone();
		stu.address = (Address) this.getAddress().clone();
        return stu;
	}
	
	public String toString() {
		return "[name:"+name+";address:" + address.toString()+";hobbies:" + hobbies.toString() + "]";
	}
	
}

改造后,再测试

	public static void main(String[] args) throws CloneNotSupportedException {
		List<String> hobbies = new ArrayList<>();
		hobbies.add("running");
		hobbies.add("skiing");
		
		Address address = new Address("China.Taiwan");
		Student stu = new Student("zhangsan", address, hobbies);
		Student stuCopy = stu.clone();
		System.out.println(stuCopy);
		
		stu.setName("lisi");
		hobbies.add("climing");
		address.setProvice("Hainan");
		
		System.out.println(stu);
		System.out.println(stuCopy);
	}

通过控制台输出可以看到

[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing]]
[name:lisi;address:[provice:Hainan];hobbies:[running, skiing, climing]]
[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing, climing]]

重写Address类的clone方法后,stu对象修改address属性的时候,没有再影响到stuCopy的address属性。但是stu的hobbies属性修改,影响到了stuCopy的hobbies。这是因为hobbies的类型为List,而List对象是Java自有对象,我们不方便重写List的clone方法。如此,这里该如何处理,须要换种方式把List对象的属性值复制出来,可以使用addAll()方法复制,或使用循环方式复制。

改造Student类的clone方法

	public Student clone() throws CloneNotSupportedException {
		Student stu = (Student) super.clone();
		stu.address = (Address) this.getAddress().clone();
		List<String> hobby = new ArrayList<>();
		hobby.addAll(this.getHobbies());
		stu.hobbies = hobby;
        return stu;
	}

继续测试,可以看到,再对stu修改的时候,没有再对stuCopy的属性影响。

[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing]]
[name:lisi;address:[provice:Hainan];hobbies:[running, skiing, climing]]
[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing]]

Serializable序列化方式

序列化方式,继承Serializable接口进行序列化与反序列化进行深拷贝,这里要注意,深拷贝的时候,主对象里面涉及到的对象属性均须实现Serializable接口,否则,会出错。

改造Student类

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;


public class Student implements Cloneable, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 9007215701712486285L;

	// 姓名- 简单属性
	private String name;
	
	// 地址- 对象
	private Address address;
	
	// 爱好- 集合类型
	private List<String> hobbies;
	
	public Student(String name, Address address, List<String> hobbies) {
		this.name = name;
		this.address = address;
		this.hobbies = hobbies;
	}

	...省略get set方法...
	
	public String toString() {
		return "[name:"+name+";address:" + address.toString()+";hobbies:" + hobbies.toString() + "]";
	}
	
	public Student clone () {
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(this);
			ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bais);
			return (Student) ois.readObject();
		} catch (IOException|ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		return null;
	}
}

改造 Address类定义

import java.io.Serializable;

public class Address implements Cloneable, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 5879834545307777452L;
	
	// 省
	private String provice;
	
	public Address(String provice) {
		this.provice = provice;
	}

	public String getProvice() {
		return provice;
	}

	public void setProvice(String provice) {
		this.provice = provice;
	}
	
	public Address clone() throws CloneNotSupportedException {
		Address addr = (Address) super.clone();
        return addr;
	}
	
	public String toString() {
		return "[provice:"+ provice+"]";
	}
}

同步的,可以查看List接口的实现类ArrayList代码,亦实现Serializable接口

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    ...省略其它...
}

再测试,结果与使用clone方法实现深拷贝结果相同。

猜你喜欢

转载自blog.csdn.net/magi1201/article/details/113342940