Ubuntu Filecoin django typeAliases 数据算法 Flutter acm docker安装 facebook templates vue论坛 vue绑定事件 vue动态绑定class 后台网站模板 在线考试系统代码 jq延时 pr序列设置哪个好 mysql修改字段值 时间戳java java时间戳 python环境搭建 python中items python程序 python使用正则表达式 java编程 java实例 java获取年份 java开发环境搭建 java学习教程 学习java基础 java语言是什么 linux服务器登录 模拟人生2夜生活 谷歌地球用不了 findall c4dr19 密码翻译 jq循环 圣武枪魂 ps字体描边
当前位置: 首页 > 学习教程  > 编程语言

Java对象浅拷贝和深拷贝

2021/1/28 23:48:33 文章标签:

Java中的数据类型分为基本数据类型和引用数据类型。 基于基本数据类型和引用类型拷贝时候的数据传递,Java对于拷贝,分为浅拷贝(swallow copy)和深拷贝(deep copy)。 看一个例子,一个Student对象…

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方法实现深拷贝结果相同。

 


本文链接: http://www.dtmao.cc/news_show_650301.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?