Java中深拷贝与浅拷贝
在谈论深拷贝、浅拷贝之前,首先要理解什么是值类型?什么是引用类型?这对于理解深拷贝、浅拷贝很关键。
在Java
的世界,我们要习惯用引用去操作对象。在Java
中,像数组、类Class
、枚举Enum
、Integer
包装类等等,就是典型的引用类型,所以操作时一般来说采用的也是引用传递的方式;
但是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;
}
......
}
此时,这两个类的关系如下:
浅拷贝
浅拷贝,它的特性体现在这个“浅”字上面。
- 浅拷贝代码实现:
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);
}
}
打印出的结果:
根据这里执行的结果得出结论:
-
teacher == teacher1打印出的结果是false,则说明是创建了一个新的对象
-
修改克隆出的对象的name和age属性,发现并不会影响原对象的属性值
-
修改克隆出的对象的学生属性,导致原对象的学生属性也被修改了
浅拷贝中 值类型的字段会复制一份,而引用类型的字段拷贝的仅仅是引用地址,而该引用地址指向的实际对象空间其实只有一份。
(1)引出的问题,String类型是属于引用类型,为什么它也和基本数据类型一样是复制值?后面再研究这个问题
深拷贝
深拷贝相对于浅拷贝,在复制基本数据类型时,也会将引用类型所指向的内存拷贝一份。如下图。
- 深拷贝代码实现:
深拷贝需要本例中的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();
}
}
......
}
-
主程序类与浅拷贝主程序代码一致
-
执行结果
根据执行结果得出:克隆出的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();
}
}*/
}
- 主程序执行结果:
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
}
}
str2指向的是常量池中的Hello字符串
str1指向的是在堆内存中创建的String对象,内容是Hello字符串。
在java中String通过常量赋值认为是基本数据类型,通过new关键字创建的对象则是引用类型。