java中的深拷贝与浅拷贝

Java中深拷贝与浅拷贝

在谈论深拷贝、浅拷贝之前,首先要理解什么是值类型?什么是引用类型?这对于理解深拷贝、浅拷贝很关键。

Java的世界,我们要习惯用引用去操作对象。在Java中,像数组、类Class、枚举EnumInteger包装类等等,就是典型的引用类型,所以操作时一般来说采用的也是引用传递的方式;

但是Java中基础数据类型,如int这些基本类型,操作时一般采取的则是值传递的方式,所以有时候也称它为值类型。


为了方便案例的演示,首先准备两个类,一个是教师类,一个是学生类,这里以一个老师只教一个学生为例,教师类中包含自己所教的学生。

  • 教师类,Teacher
public class Teacher {
	/**
	 * 姓名
	 */
	private String name;
	/**
	 * 年龄
	 */
	private int age;
	/**
	 * 学生
	 */
	private Student stu;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	......
}
  • 学生类,Student
public class Student {
	/**
	 * 学号
	 */
	private long stuNo;
	/**
	 * 姓名
	 */
	private String stuName;

	public long getStuNo() {
		return stuNo;
	}

	public void setStuNo(long stuNo) {
		this.stuNo = stuNo;
	}
    ......
}

此时,这两个类的关系如下:

image-20211213165802562

浅拷贝

浅拷贝,它的特性体现在这个“浅”字上面。

  • 浅拷贝代码实现:
public class Teacher implements Cloneable{
	/**
	 * 姓名
	 */
	private String name;
	/**
	 * 年龄
	 */
	private int age;
	/**
	 * 学生
	 */
	private Student stu;

	@Override
	public Teacher clone() {
		try {
			return (Teacher) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}
   	......
}
  • 主程序
public class ShallowMain {
	public static void main(String[] args) {
		Student student = new Student(2016021006, "李四");
		Teacher teacher = new Teacher("鲁班", 24);
		teacher.setStu(student);

		// 克隆教师对象
		Teacher teacher1 = teacher.clone();

		System.out.println(teacher == teacher1);
		// 原对象
		System.out.println(teacher);
		// 克隆对象
		System.out.println(teacher1);

		System.out.println("-------------------修改克隆对象的name以及年龄---------------------");
		// 修改克隆对象的name以及年龄
		teacher1.setName("鲁班大师");
		teacher1.setAge(34);

		// 原对象
		System.out.println("原对象:" + teacher);
		// 克隆对象
		System.out.println("克隆对象:" + teacher1);

		System.out.println("-------------------修改原对象的学生属性---------------------");
		// 修改原对象的学生属性
		student.setStuName("张三");
		student.setStuNo(666666);

		// 原对象
		System.out.println("原对象:" + teacher);
		// 克隆对象
		System.out.println("克隆对象:" + teacher1);
	}
}

打印出的结果:

image-20211213172207396


根据这里执行的结果得出结论:

  • teacher == teacher1打印出的结果是false,则说明是创建了一个新的对象

  • 修改克隆出的对象的name和age属性,发现并不会影响原对象的属性值

  • 修改克隆出的对象的学生属性,导致原对象的学生属性也被修改了

浅拷贝中 值类型的字段会复制一份,而引用类型的字段拷贝的仅仅是引用地址,而该引用地址指向的实际对象空间其实只有一份。

image-20211214100413129


(1)引出的问题,String类型是属于引用类型,为什么它也和基本数据类型一样是复制值?后面再研究这个问题

深拷贝

深拷贝相对于浅拷贝,在复制基本数据类型时,也会将引用类型所指向的内存拷贝一份。如下图。

image-20211214101427220

  • 深拷贝代码实现:

深拷贝需要本例中的Student也实现Cloneable接口,这样Student也可以拷贝。

  • Student类
public class Student implements Cloneable{
	/**
	 * 学号
	 */
	private long stuNo;
	/**
	 * 姓名
	 */
	private String stuName;

	@Override
	public Student clone() {
		try {
			return (Student) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}
	
	......
}
  • Teacher类
public class Teacher implements Cloneable{
	/**
	 * 姓名
	 */
	private String name;
	/**
	 * 年龄
	 */
	private int age;
	/**
	 * 学生
	 */
	private Student stu;

	@Override
	public Teacher clone() {
		try {
            //先克隆Teacher对象
			Teacher teacher = (Teacher) super.clone();
            //克隆Student对象,复值给克隆出的teacher对象中的stu属性
			teacher.stu = stu.clone();
			return teacher;
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}
	
	......
}
  • 主程序类与浅拷贝主程序代码一致

  • 执行结果

image-20211214102620298

根据执行结果得出:克隆出的Teacher对象已经是相互独立的互不干扰。


深拷贝的另一种实现方式:反序列化实现对象深拷贝

  • Teacher类
public class Teacher implements Serializable{
	/**
	 * 姓名
	 */
	private String name;
	/**
	 * 年龄
	 */
	private int age;
	/**
	 * 学生
	 */
	private Student stu;

	@Override
	public Teacher clone() {
		/*try {
			Teacher teacher = (Teacher) super.clone();
			teacher.stu = stu.clone();
			return teacher;
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}*/

		try {
			//将当前对象序列化到字节流
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
			objectOutputStream.writeObject(this);
			//反序列化创建对象
			ObjectInputStream objectInputStream = new ObjectInputStream(
					new ByteArrayInputStream(outputStream.toByteArray()));
			return  (Teacher) objectInputStream.readObject();
		} catch (IOException | ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}
}
  • 反序列创建对象,需要被引用的子类可以被序列化。也就是Student类需要实现Serializable接口。
public class Student implements Serializable {
	/**
	 * 学号
	 */
	private long stuNo;
	/**
	 * 姓名
	 */
	private String stuName;

	/*@Override
	public Student clone() {
		try {
			return (Student) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}*/
}
  • 主程序执行结果:

image-20211214103954413

String引用类型

String常见的两种创建方式,示例代码:

public class StringMain {
	public static void main(String[] args) {
		String str1 = new String("Hello");
		String str2 = "Hello";
		System.out.println(str1 == str2); //false
	}
}

image-20211214111731292

str2指向的是常量池中的Hello字符串

str1指向的是在堆内存中创建的String对象,内容是Hello字符串。

在java中String通过常量赋值认为是基本数据类型,通过new关键字创建的对象则是引用类型。

原文地址:https://www.cnblogs.com/YpfBolg/p/15687056.html