Java中浅复制和深复制

浅析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 概念

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



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 概念

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



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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,拷贝(Shallow Copy)和拷贝(Deep Copy)是用于复制对象的两种不同方式。 拷贝是创建一个新对象,并将原始对象的非静态字段的值复制到新对象中。新对象和原始对象中的引用类型字段将引用相同的对象。换句话说,拷贝只复制对象的引用,而不是对象本身。 拷贝是创建一个新对象,并将原始对象的所有字段的值复制到新对象中,包括引用类型字段。这意味着在拷贝中,即使原始对象中的引用类型字段引用相同的对象,新对象也将有自己的副本。 为了实现拷贝,可以使用`clone()`方法。这个方法是`Object`类的一个方法,需要在要复制的类中实现`Cloneable`接口。然后,可以使用`clone()`方法来创建一个新对象,它将具有与原始对象相同的字段值。 要实现拷贝,可以通过以下几种方式之一: 1. 使用序列化和反序列化:将对象写入字节流并读取回来,这将创建一个与原始对象相同但独立的新对象。 2. 使用拷贝构造函数或拷贝工厂方法:在类中定义一个构造函数或静态工厂方法,它接受另一个对象作为参数,并复制其字段值到新对象中。 3. 递归复制对象的所有引用类型字段:对于每个引用类型字段,创建一个新对象并复制其字段值。 需要注意的是,拷贝可能会导致性能开销较大,尤其是在对象图很大或存在循环引用的情况下。因此,在进行拷贝时,需要仔细考虑其对性能的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值