1. 程式人生 > >淺談java中的淺拷貝(淺複製)和深拷貝(深複製)

淺談java中的淺拷貝(淺複製)和深拷貝(深複製)

淺拷貝:
淺拷貝又稱為淺複製,淺克隆,淺拷貝是指拷貝時只拷貝物件本身(包括物件中的基本變數),而不拷貝物件包含的引用所指向的物件,拷貝出來的物件的所有變數的值都含有與原來物件相同的值,而所有對其他物件的引用都指向原來的物件,簡單地說,淺拷貝只拷貝物件不拷貝引用。
深拷貝:
深拷貝又稱為深複製,深克隆,深拷貝不僅拷貝物件本身,而且還拷貝物件包含的引用所指向的物件,拷貝出來的物件的所有變數(不包含那些引用其他物件的變數)的值都含有與原來物件的相同的值,那些引用其他物件的變數將指向新複製出來的新物件,而不指向原來的物件,簡單地說,深拷貝不僅拷貝物件,而且還拷貝物件包含的引用所指向的物件。

再簡單的說就是淺拷貝只拷貝物件,不拷貝引用。深拷貝物件引用全拷貝
java中常用的拷貝操作有三種:(1)操作符= (2)拷貝建構函式 (3)clone( )方法,由於java不支援運算子過載,所以我們不能在自己定義的類中定級操作符=操作。拷貝建構函式就不多說了,我們經常遇到。主要說一下clone方法,如果我們想要使自己定義的物件能夠實現深拷貝,就需要改寫從Object類中繼承來的clone方法,clone方法在Object類中是protected許可權,是為了防止意外的支援clone操作,所以我們需要把它改寫成public許可權的

看如下程式碼:

public class CloneTest{
    public static void main(String[] args) {
        People p = new People("xiaowang",10);
        Employee employee = new Employee("zhangsan", 20,p);
        Employee newEmployee = (Employee)employee.clone();
        newEmployee.p.name = "lisi";
        newEmployee.p.age = 30
; System.out.println("employee.p.name="+employee.p.name+" "+"employee.p.age="+employee.p.age); System.out.println("newEmployee.p.name="+newEmployee.p.name+" "+"newEmployee.p.age="+newEmployee.p.age); } } class People { String name; int age; People(String name,int age) { this.name = name; this.age = age; } } class
Employee implements Cloneable {
String name; int age; People p; Employee(String name,int age,People p) { this.name = name; this.age = age; this.p = p; } public Object clone() { Employee obj = null; try { obj = (Employee)super.clone(); //Object中需要識別你要克隆的物件 } catch (CloneNotSupportedException e) { System.out.println(e.toString()); } return obj; } }

執行結果為:
employee.p.name=lisi employee.p.age=30
newEmployee.p.name=lisi newEmployee.p.age=30

上述程式碼如果執行以後,會改變原來物件的employee.p.name和”employee.p.age,因為這是淺拷貝,只是拷貝了物件,而引用還是以前物件的引用,所以更改了現在物件的引用元物件的引用也是會改變
再來看一下深拷貝

public class CloneTest {
    public static void main(String[] args) {
        People p = new People("xiaowang",10);
        Employee employee = new Employee("zhangsan", 20,p);
        Employee newEmployee = (Employee)employee.clone();
        newEmployee.p.name = "lisi";
        newEmployee.p.age = 30;
        System.out.println("employee.p.name="+employee.p.name+" "+"employee.p.age="+employee.p.age);
        System.out.println("newEmployee.p.name="+newEmployee.p.name+" "+"newEmployee.p.age="+newEmployee.p.age);
    }
}

class People implements Cloneable
{
    String name;
    int age;
    People(String name,int age)
    {
        this.name = name;
        this.age = age;
    }

    public Object clone()
    {
        Object obj = null;
        try
        {
            obj = super.clone();
        } catch (CloneNotSupportedException e) 
        {
            System.out.println(e.toString());
        }
        return obj;
    }
}

class Employee implements Cloneable
{
    String name;
    int age;
    People p;

    Employee(String name,int age,People p)
    {
        this.name = name;
        this.age = age;
        this.p = p;
    }

    public Object clone()
    {
        Employee obj = null;
        try
        {
            obj = (Employee)super.clone();  //Object中需要識別你要克隆的物件
        } catch (CloneNotSupportedException e) 
        {
            System.out.println(e.toString());
        }
        obj.p = (People)p.clone();
        return obj;
    }
}

執行結果為:
employee.p.name=xiaowang employee.p.age=10
newEmployee.p.name=lisi newEmployee.p.age=30

現在我們改進成了深拷貝,就會發現不會改變原來物件的employee.p.name和”employee.p.age,說明我們深拷貝拷貝了物件和物件的引用

java裡面還有一種實現深拷貝的方法,就是運用物件的序列化機制,記得我是在java核心技術卷第九版卷二第一章看到的,很有趣。
序列化機制有一種很有趣的用法:提供了一種克隆物件的簡便途徑,只要對應的類是可序列化的就可以,做法是:直接將物件序列化到輸出流中,然後將其讀回,這樣產生的新的物件是對現有物件的一個深拷貝。在此過程中,我們不必將物件寫到檔案中,因為可以用ByteArrayOutputStream將資料儲存到位元組陣列中。
示例程式碼:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
import java.util.GregorianCalendar;

public class SerialCloneTest {
    public static void main(String[] args)
    {
        Employee harry = new Employee("Harry Hacker",35000,1929,10,1);
        Employee harry2 = (Employee)harry.clone();
        harry.raiseSalary(10);
        System.out.println(harry);
        System.out.println(harry2);
    }
}
class SerialCloneable implements Cloneable,Serializable
{
    public Object clone()
    {
        try 
        {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bout);
            out.writeObject(this);
            out.close();

            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bin);
            Object ret = in.readObject();
            in.close();
            return ret;
        } catch (Exception e)
        {
            return null;
        }
    }
}

class Employee extends SerialCloneable
{
    private String name;
    private double salary;
    private Date hireDay;

    public Employee(String n,double s,int year,int month,int day)
    {
        name = n;
        salary = s;
        GregorianCalendar calendar = new GregorianCalendar(year,month - 1,day);
        hireDay = calendar.getTime();
    }
    public String getName() {
        return name;
    }
    public double getSalary() {
        return salary;
    }
    public Date getHireDay() {
        return hireDay;
    }
    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
    }

    public String toString()
    {
        return getClass().getName()+"[name="+name+",salary="+salary+",hireDay="+hireDay+"]";
    }
}

上面的示例程式碼是自己照著書敲得,敲一遍也明白了點,不過我們應該當心用這種方法,儘管它很靈巧,但是它通常會比顯示的構建新的物件並複製或克隆資料域的克隆方法慢很多