java中的深拷貝與淺拷貝
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關鍵字建立的物件則是引用型別。