1. 程式人生 > >Java例項 Part6:Java中的克隆

Java例項 Part6:Java中的克隆

目錄

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可以將物件儲存在記憶體中,這樣就不必產生一個本地檔案來完成序列化的功能。
***

假克隆、淺克隆和深克隆的應用範圍

假克隆 基本資料型別
淺克隆 基本資料型別、不可變引用型別
深克隆 可變引用型別