淺談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+"]";
    }
}

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章