Java中浅复制和深复制

时间:2022-08-23 14:40:06

浅析Java中浅复制和深复制

零、赋值运算符

如果只是复制一个基本数据类型的变量,直接使用赋值运算符即可;

int num = 325;
int birthday = num;

如果复制一个对象,直接使用赋值运算符,出现的情况会是什么?

class Student {
	private int number;
	public int getNumber() {
		return number;
	}
	public void setNumber(int number) {
		this.number = number;
	}
}
public class Test {
	public static void main(String args[]) {
		Student stu1 = new Student();
		stu1.setNumber(325);
		Student stu2 = stu1;
		System.out.println("stu1:" + stu1.getNumber()); // stu1:325
		System.out.println("stu2:" + stu2.getNumber()); // stu2:325
		stu2.setNumber(829);
		System.out.println("stu1:" + stu1.getNumber()); // stu1:829
		System.out.println("stu2:" + stu2.getNumber()); // stu2:829
	}
}

运行后可看到在执行stu2.setNumber(829);语句后,stu1的状态值也改变了;

这是因为stu2 = stu1语句将stu1的引用赋值给stu2,因此stu1和stu2指向内存堆中的同一个对象;


如何复制一个对象?

答:通过覆盖Object类的clone()方法,其签名是

protected native Object clone() throws CloneNotSupportedException;

因为每个类都是直接或间接继承于Object类,因此它们都含有clone()方法,但该方法是protected,不能在包外访问;


一、浅复制

1 概念

创建一个对象,拥有原始对象属性值的一份精确拷贝,即属性是基本类型,拷贝基本类型的值;属性是引用类型,拷贝内存地址;

Java中浅复制和深复制


2 方法

被复制的类需要实现Cloneable接口;

覆盖clone()方法,访问修饰符设置为public;


3 代码实现

class Student implements Cloneable{
	private int number;
	public int getNumber() {
		return number;
	}
	public void setNumber(int number) {
		this.number = number;
	}
	@Override
	public Object clone() {
		Student stu = null;
		try{
			stu = (Student)super.clone();
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return stu;
	}
}
public class Test {
	public static void main(String args[]) {
		Student stu1 = new Student();
		stu1.setNumber(325);
		Student stu2 = (Student)stu1.clone();
		System.out.println("stu1:" + stu1.getNumber()); // stu1:325
		System.out.println("stu2:" + stu2.getNumber()); // stu2:325
		stu2.setNumber(829);
		System.out.println("stu1:" + stu1.getNumber()); // stu1:325
		System.out.println("stu2:" + stu2.getNumber()); // stu2:829
	}
}


但如果复制的类中有实例变量是引用变量,则根据浅复制定义,复制的只是内存地址;

class Address  {
	private String add;
	public String getAdd() {
		return add;
	}
	public void setAdd(String add) {
		this.add = add;
	}
}
class Student implements Cloneable{
	private int number;
	private Address addr;
	public Address getAddr() {
		return addr;
	}
	public void setAddr(Address addr) {
		this.addr = addr;
	}
	public int getNumber() {
		return number;
	}
	public void setNumber(int number) {
		this.number = number;
	}
	@Override
	public Object clone() {
		Student stu = null;
		try{
			stu = (Student)super.clone();
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return stu;
	}
}
public class Test {
	public static void main(String args[]) {
		Address addr = new Address();
		addr.setAdd("HZ");
		Student stu1 = new Student();
		stu1.setNumber(325);
		stu1.setAddr(addr);
		Student stu2 = (Student)stu1.clone();
		System.out.println("stu1:" + stu1.getNumber() + ",addr:" + stu1.getAddr().getAdd()); // stu1:325,addr:HZ
		System.out.println("stu2:" + stu2.getNumber() + ",addr:" + stu2.getAddr().getAdd()); // stu2:325,addr:HZ
		addr.setAdd("NJ");
		System.out.println("stu1:" + stu1.getNumber() + ",addr:" + stu1.getAddr().getAdd()); // stu1:325,addr:NJ
		System.out.println("stu2:" + stu2.getNumber() + ",addr:" + stu2.getAddr().getAdd()); // stu2:325,addr:NJ
	}
}


二、深复制

1 概念

拷贝所有属性,并拷贝属性所指向的动态分配的内存;

Java中浅复制和深复制


2 方法

在浅复制的代码中做小改动;

class Address implements Cloneable { // 实现Cloneable接口
	private String add;
	public String getAdd() {
		return add;
	}
	public void setAdd(String add) {
		this.add = add;
	}
	@Override // Address类中覆盖clone()
	public Object clone() {
		Address addr = null;
		try{
			addr = (Address)super.clone();
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return addr;
	}
}
class Student implements Cloneable{
	private int number;
	private Address addr;
	public Address getAddr() {
		return addr;
	}
	public void setAddr(Address addr) {
		this.addr = addr;
	}
	public int getNumber() {
		return number;
	}
	public void setNumber(int number) {
		this.number = number;
	}
	@Override
	public Object clone() {
		Student stu = null;
		try{
			stu = (Student)super.clone();	//浅复制
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
		stu.addr = (Address)addr.clone();	//深度复制,对引用变量进行复制
		return stu;
	}
}
public class Test {
	public static void main(String args[]) {
		Address addr = new Address();
		addr.setAdd("HZ");
		Student stu1 = new Student();
		stu1.setNumber(325);
		stu1.setAddr(addr);
		Student stu2 = (Student)stu1.clone();
		System.out.println("stu1:" + stu1.getNumber() + ",addr:" + stu1.getAddr().getAdd()); // stu1:325,addr:HZ
		System.out.println("stu2:" + stu2.getNumber() + ",addr:" + stu2.getAddr().getAdd()); // stu2:325,addr:HZ
		addr.setAdd("NJ");
		System.out.println("stu1:" + stu1.getNumber() + ",addr:" + stu1.getAddr().getAdd()); // stu1:325,addr:NJ
		System.out.println("stu2:" + stu2.getNumber() + ",addr:" + stu2.getAddr().getAdd()); // stu2:325,addr:HZ
	}
}

但上述方法有个缺点——对于比较复杂的对象的深复制,重写clone()方法也会繁琐、易出错,因此使用序列化实现深复制;


3 改进——序列化实现深复制

序列化是将整个对象写入一个持久化存储文件中并在需要的时候把它取出来,表名当读取出来时需要真个对象的一个拷贝,即深复制需要的东西;

先使对象实现Serializable接口,再把对象的拷贝写进一个流里,然后从流中读取以重建对象;

import java.io.*;
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException{
    	Address addr = new Address();
    	addr.setAdd("HZ");
    	Student stu1 = new Student();
    	stu1.setNumber(325);
    	stu1.setAddr(addr);
    	Student stu2 = (Student)stu1.deepClone();
    	addr.setAdd("NJ");
    	System.out.println(stu1.getAddr().getAdd()); // NJ
    	System.out.println(stu2.getAddr().getAdd()); // HZ
    }
}
class Address implements Serializable{
	private String add;
	public String getAdd(){
		return add;
	}
	public void setAdd(String add){
		this.add = add;
	}
}
class Student implements Serializable{
	private int number;
	private Address addr;
	public Address getAddr(){
		return addr;
	}
	public void setAddr(Address addr){
		this.addr = addr;
	}
	public int getNumber(){
		return number;
	}
	public void setNumber(int number){
		this.number = number;
	}
	public Object deepClone() throws IOException, ClassNotFoundException{
		// 将对象写进流里
		ByteArrayOutputStream bo = new ByteArrayOutputStream();
		ObjectOutputStream os = new ObjectOutputStream(bo);
		os.writeObject(this);
		// 从流利读取对象
		ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
		ObjectInputStream is = new ObjectInputStream(bi);
		return (is.readObject());
	}
}


三、选择浅复制/深复制的场景

1  若对象的属性全是基本类型的,则使用浅复制

2 若对象有引用属性,则基于集体需求选择浅复制或深复制,即若对象引用任何时候都不会改变,使用浅复制;若对象引用经常改变,使用深复制