Java例項 Part6:Java中的克隆
阿新 • • 發佈:2018-11-04
目錄
Part6:Java中的克隆
@
***
Example01:Java物件的假克隆
- 物件的克隆是Java中的一項高階技術,獲得與其相同的物件。
基本資料型別可以使用“=”來進行克隆,此時兩個變數除了相等是沒有任何關係的。而對於引用型別資料不能簡單地使用“=”進行克隆,這與Java的記憶體空間使用有關。
Java將記憶體空間分成兩塊,即棧和堆。在棧中儲存基本型別和引用變數;在堆中儲存物件。對於引用變數而言,使用“=”將修改引用,而不是複製堆中的物件。此時兩個引用變數將指向同一個物件。因此,如果一個變數對其修改則會改變另一個變數。
執行結果:
程式碼實現:
public class Employee { private String name; private int age; //省略set()和get()方法 @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + '}'; } public static void main(String[] args) { System.out.println("-----克隆之前:--------"); Employee employee1 = new Employee(); employee1.setName("hyn"); employee1.setAge(20); System.out.println("員工1的資訊:\n"+employee1); System.out.println("-----克隆之後:--------"); Employee employee2 = employee1; //將employee1賦值給employee2 employee2.setName("azw"); employee2.setAge(21); System.out.println("員工1的資訊:\n"+employee1); System.out.println("員工2的資訊:\n"+employee2); } }
Example02:Java物件的淺克隆
在克隆物件時,如果物件的成員變數是基本資料型別,則使用淺克隆即可完成。如果物件的成員變數包括可變引用型別,則需要深克隆。
執行結果:
程式碼實現:
//Address.java public class Address { private String state; //所在國家 private String province; //所在省 private String city; //所在城市 public Address(String state, String province, String city) { this.state = state; this.province = province; this.city = city; } //省略set()和get()方法 @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("國家:"+state+","); sb.append("省:"+province+","); sb.append("市:"+city); return sb.toString(); } } //Employee.java public class Employee implements Cloneable{ private String name; private int age; private Address address; public Employee(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } //省略set()和get()方法 @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("姓名:"+name+","); sb.append("年齡:"+age+","); sb.append("\n地址:"+address); return sb.toString(); } @Override public Employee clone() throws CloneNotSupportedException { //實現淺克隆 Employee employee = (Employee) super.clone(); return employee; } }
測試程式碼:
class Test {
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("*****克隆之前:******");
Address address = new Address("中國", "湖北", "武漢");
Employee employee1 = new Employee("azw", 20, address);
System.out.println("員工1的資訊:\n" + employee1); //employee1的資訊
System.out.println("*****克隆之後:******");
Employee employee2 = employee1.clone(); //使用克隆建立Employee2
employee2.getAddress().setState("中國"); //修改地址
employee2.getAddress().setProvince("黑龍江");
employee2.getAddress().setCity("哈爾濱");
employee2.setName("hyn");
employee2.setAge(21);
System.out.println("員工1的資訊:\n" + employee1);
System.out.println("員工2的資訊:\n" + employee2);
}
}
- 如果引用型別是不可變的,如String類物件,則不必進行深克隆。
***
Example03:Java物件的深克隆
- 如果類的成員變數中包括可變引用型別,則需進行深克隆。
執行結果:
程式碼實現:
//Address.java
public class Address implements Cloneable{
private String state; //所在國家
private String province; //所在省
private String city; //所在城市
public Address(String state, String province, String city) {
this.state = state;
this.province = province;
this.city = city;
}
//省略set()和get()方法
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("國家:"+state+",");
sb.append("省:"+province+",");
sb.append("市:"+city);
return sb.toString();
}
//---------------------------
@Override
public Address clone() throws CloneNotSupportedException {
//Address類中的域不是基本型別就是不可變型別,所以可以直接使用淺克隆
Address address = (Address) super.clone();
return address;
}
//---------------------------
}
//Employee.java
public class Employee implements Cloneable{
private String name;
private int age;
private Address address;
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
//省略set()和get()方法
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("姓名:"+name+",");
sb.append("年齡:"+age+",");
sb.append("\n地址:"+address);
return sb.toString();
}
@Override
public Employee clone() throws CloneNotSupportedException { //實現深克隆
Employee employee = (Employee) super.clone();
//---------------------------------
employee.address = address.clone();
//---------------------------------
return employee;
}
}
//測試程式碼同Example02測試程式碼.
- 要點:通常情況下,需要用到克隆物件時都需要使用深克隆。
***
Example04:序列化與物件克隆
如果類的成員變數比較複雜,例如使用了多個可變的引用型別,使用clone()方法是非常麻煩的,所以可以考慮序列化的方式完成克隆。
執行結果:
程式碼實現:
import java.io.Serializable;
public class Employee implements Serializable {
//同Example04中Employee.java的程式碼
}
public class Address implements Serializable {
//同Example04中Assress.java的程式碼
}
測試程式碼:
class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("*****序列化之前:******");
Address address = new Address("中國", "湖北", "武漢");
Employee employee1 = new Employee("azw", 20, address);
System.out.println("員工1的資訊:\n" + employee1); //employee1的資訊
System.out.println("*****序列化之後:******");
Employee employee2 = null;
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\employee.txt"));
out.writeObject(employee1); //將物件寫入到本地檔案中
ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:\\employee.txt"));
employee2 = (Employee)in.readObject(); //從本地檔案中讀取物件
if (employee2 != null) {
employee2.getAddress().setState("中國"); //修改地址
employee2.getAddress().setProvince("黑龍江");
employee2.getAddress().setCity("哈爾濱");
employee2.setName("hyn");
employee2.setAge(21);
System.out.println("員工1的資訊:\n" + employee1);
System.out.println("員工2的資訊:\n" + employee2);
}
}
}
要點:進行序列化的類需要實現Serializable介面,該介面中並沒有定義任何方法,是一個標識介面。如果類中有可變的引用型別成員變數,則該變數需要實現Serializable介面。本例項採用將物件寫入本地檔案的方式完成序列化。
***
Example05:深克隆和序列化的效率比較
- 通過使用這兩種方式克隆100000個物件,並輸出花費的時間來比較這兩種方法的效率。
執行結果:
程式碼實現:
import java.io.Serializable;
public class Employee implements Cloneable,Serializable {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("姓名:"+name+",");
sb.append("年齡:"+age+",");
return sb.toString();
}
@Override
public Employee clone() throws CloneNotSupportedException { //使用父類的clone()方法實現深克隆
Employee employee = (Employee) super.clone();
return employee;
}
}
測試程式碼:
import java.io.*;
import java.util.ArrayList;
import java.util.List;
class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
List<Employee> employees = new ArrayList<Employee>(); //建立列表儲存物件
Employee employee = new Employee("azw", 20); //建立物件
long currentTime = System.currentTimeMillis(); //獲得當前系統時間
//使用克隆方式獲得物件
for (int i = 0;i<100000;i++){
employees.add(employee.clone());
}
System.out.println("克隆花費的時間:"+(System.currentTimeMillis()-currentTime)+"毫秒");
currentTime = System.currentTimeMillis(); //獲得當前系統時間
for (int i = 0;i<100000;i++){
ByteArrayOutputStream bout = new ByteArrayOutputStream(); //建立位元組陣列輸出流
ObjectOutputStream out = new ObjectOutputStream(bout); //建立物件輸出流
out.writeObject(employee); //將物件寫入到輸出流中
//獲得位元組輸出流內容
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin); //建立物件輸入流
employees.add((Employee) in.readObject()); //讀取物件
}
System.out.println("序列化花費的時間:"+(System.currentTimeMillis()-currentTime)+"毫秒");
}
}
要點:使用ByteArrayOutputStream和ByteArrayInputStream可以將物件儲存在記憶體中,這樣就不必產生一個本地檔案來完成序列化的功能。
***
假克隆、淺克隆和深克隆的應用範圍
假克隆 | 基本資料型別 |
---|---|
淺克隆 | 基本資料型別、不可變引用型別 |
深克隆 | 可變引用型別 |