天天看點

java中指派語句(=)與淺拷貝與深拷貝弄出來的對象有什麼差別

文章目錄

  • ​​1、建立對象的5種方式​​
  • ​​2、Clone 方法​​
  • ​​3、基本類型和引用類型​​
  • ​​4、淺拷貝與深拷貝​​
  • ​​4.1、淺拷貝​​
  • ​​4.2、深拷貝​​
  • ​​4.3、如何實作深拷貝?​​
  • ​​4.3.1、讓每個引用類型屬性内部都重寫clone() 方法​​
  • ​​4.3.2、利用序列化​​
  • ​​4.3.2、使用開源工具類​​
  • ​​5、指派語句(=)與淺拷貝與深拷貝弄出來的對象對比​​
  • ​​5.1、指派語句(=):​​
  • ​​5.2、淺拷貝:​​
  • ​​5.3、深拷貝:​​
  • ​​5.4、總結一下:​​

探讨一下淺拷貝、深拷貝的原理,實作,差別。

并對比通過指派、淺拷貝、深拷貝三種不同方式弄出來的對象之間的equals()和hashCode()的差别

1、建立對象的5種方式

  • 通過 new 關鍵字
  • 這是最常用的一種方式,通過 new 關鍵字調用類的有參或無參構造方法來建立對象。比如 Object obj = new Object();
  • 通過 Class 類的 newInstance() 方法

     - 這種預設是調用類的無參構造方法建立對象。比如 Person p2 = (Person) Class.forName(“com.ys.test.Person”).newInstance();

  • 通過 Constructor 類的 newInstance 方法
  • 這和第二種方法類時,都是通過反射來實作。通過 java.lang.relect.Constructor 類的 newInstance() 方法指定某個構造器來建立對象。
  • Person p3 = (Person) Person.class.getConstructors()[0].newInstance();
  • 實際上第二種方法利用 Class 的 newInstance() 方法建立對象,其内部調用還是 Constructor 的 newInstance() 方法。
  • 利用 Clone 方法
  • Clone 是 Object 類中的一個方法,通過 對象A.clone() 方法會建立一個内容和對象 A 一模一樣的對象 B,clone 克隆,顧名思義就是建立一個一模一樣的對象出來。
  • Person p4 = (Person) p3.clone();
  • 反序列化
  • 序列化是把堆記憶體中的 Java 對象資料,通過某種方式把對象存儲到磁盤檔案中或者傳遞給其他網絡節點(在網絡上傳輸)。而反序列化則是把磁盤檔案中的對象資料或者把網絡節點上的對象資料,恢複成Java對象模型的過程。

2、Clone 方法

本篇部落格涉及的是 Java 的深拷貝和淺拷貝,其實作方式正是通過調用 Object 類的 clone() 方法來完成。在 Object.class 類中,源碼為:

protected native Object clone() throws CloneNotSupportedException;      

這是一個用 native 關鍵字修飾的方法,關于native(native 用來修飾方法,用 native 聲明的方法表示告知 JVM 調用,該方法在外部定義,我們可以用任何語言去實作它,一般對于java來說,調用native修飾的方法都是用c來寫的。 簡單地講,一個native Method就是一個 Java 調用非 Java 代碼的接口。),不了解也沒關系,隻需要知道用 native 修飾的方法就是告訴作業系統,這個方法我不實作了,讓作業系統去實作。具體怎麼實作我們不需要了解,隻需要知道 clone方法的作用就是複制對象,産生一個新的對象。那麼這個新的對象和原對象是什麼關系呢?

3、基本類型和引用類型

在 Java 中基本類型和引用類型的差別。

在 Java 中資料類型可以分為兩大類:基本類型和引用類型。

  • 基本類型也稱為值類型,分别是字元類型 char,布爾類型 boolean以及數值類型 byte、short、int、long、float、double。
  • 引用類型則包括類、接口、數組、枚舉等。

Java 将記憶體空間分為堆和棧。基本類型直接在棧中存儲數值,而引用類型是将引用放在棧中,實際存儲的值是放在堆中,通過棧中的引用指向堆中存放的資料。

java中指派語句(=)與淺拷貝與深拷貝弄出來的對象有什麼差別

上圖定義的 a 和 b 都是基本類型,其值是直接存放在棧中的;而 c 和 d 是 String 聲明的,這是一個引用類型,引用位址是存放在 棧中,然後指向堆的記憶體空間。

下面 d = c;這條語句表示将 c 的引用指派給 d,那麼 c 和 d 将指向同一塊堆記憶體空間。

4、淺拷貝與深拷貝

深拷貝和淺拷貝是隻針對Object和Array這樣的引用資料類型的。

深拷貝和淺拷貝的示意圖大緻如下:

java中指派語句(=)與淺拷貝與深拷貝弄出來的對象有什麼差別

淺拷貝隻複制指向某個對象的指針,而不複制對象本身,新舊對象還是共享同一塊記憶體(這句話有待商榷)。但深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享記憶體,修改新對象不會改到原對象。

4.1、淺拷貝

淺拷貝:建立一個新對象,然後将目前對象的非靜态字段複制到該新對象,如果字段是值類型的,那麼對該字段執行複制;如果該字段是引用類型的話,則複制引用但不複制引用的對象。是以,原始對象及其副本 引用同一個對象。

通過代碼執行個體來看一下淺拷貝,代碼如下:

package com.wlw.testjava8.testCopy;

public class Address {

    private String provices;
    private String city;
    public void setAddress(String provices,String city){
        this.provices = provices;
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [provices=" + provices + ", city=" + city + "]";
    }
}      
package com.wlw.testjava8.testCopy;

public class Person implements Cloneable{
    public String pname;
    public int page;
    public Address address;
    public Person() {}

    public Person(String pname,int page){
        this.pname = pname;
        this.page = page;
        this.address = new Address();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void setAddress(String provices,String city ){
        address.setAddress(provices, city);
    }
    public void display(String name){
        System.out.println(name+":"+"pname=" + pname + ", page=" + page +","+ address);
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }
}      

這是一個我們要進行指派的原始類 Person。下面我們産生一個 Person 對象,并調用其 clone 方法複制一個新的對象。

注意:調用對象的 clone 方法,必須要讓類實作 Cloneable 接口,并且覆寫 clone 方法。

測試淺拷貝:

package com.wlw.testjava8.testCopy;

public class testCopy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person("wlw",21);
        p1.setAddress("湖北省", "武漢市");
        Person p2 = (Person) p1.clone();
        System.out.println("p1:"+p1);
        System.out.println("p1.getPname.hashCode: "+p1.getPname().hashCode());
        System.out.println("p2:"+p2);
        System.out.println("p2.getPname.hashCode: "+p2.getPname().hashCode());

        System.out.println("------------------------------------");
        System.out.println("位址修改之前:");
        p1.display("p1");
        p2.display("p2");
        p2.setAddress("湖北省", "荊州市");
        System.out.println("将複制之後的對象(p2)位址修改之後,對比變化:");
        p1.display("p1");
        p2.display("p2");
    }
}

//輸出結果:
p1:com.wlw.testjava8.testCopy.Person@610455d6
p1.getPname.hashCode: 117826
p2:com.wlw.testjava8.testCopy.Person@511d50c0
p2.getPname.hashCode: 117826
------------------------------------
位址修改之前:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
将複制之後的對象(p2)位址修改之後,對比變化:
p1:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]      

首先看原始類 Person 實作 Cloneable 接口,并且覆寫 clone 方法,它還有三個屬性,一個引用類型 String定義的 pname,一個基本類型 int定義的 page,還有一個引用類型 Address ,Address是一個自定義類,這個類也包含兩個屬性 pprovices 和 city 。

接着看測試内容,首先我們建立一個Person 類的對象 p1,其pname 為wlw,page為21,位址類 Address 兩個屬性為湖北省和武漢市。接着我們調用 clone() 方法複制另一個對象 p2,接着列印這兩個對象的内容。

從第 1 行和第 3 行列印結果:

  • p1與p2的equals()不相等,hashCode()不相等(注意p2,p4行輸出的是對象裡面的name的hashCode)
  • p2:com.wlw.testjava8.testCopy.Person@511d50c0

可以看出這是兩個不同的對象。

從第 7 行和第 8 行列印的對象内容看,原對象 p1 和克隆出來的對象 p2 内容完全相同。

代碼中我們隻是更改了克隆對象 p2 的屬性 Address 為湖北省荊州市(原對象 p1 是湖北省武漢市) ,但是從第 10 行和第 11 行列印結果來看,原對象 p1 和克隆對象 p2 的 Address 屬性都被修改了。

也就是說對象 Person 的屬性 Address,經過 clone 之後,其實隻是複制了其引用,他們(p1,p2)指向的還是同一塊堆記憶體空間,當修改其中一個對象的屬性 Address,另一個也會跟着變化。

java中指派語句(=)與淺拷貝與深拷貝弄出來的對象有什麼差別

淺拷貝:建立一個新對象,然後将目前對象的非靜态字段複制到該新對象,如果字段是值類型的,那麼對該字段執行複制;如果該字段是引用類型的話,則複制引用但不複制引用的對象。是以,原始對象及其副本 引用同一個對象。

4.2、深拷貝

弄清楚了淺拷貝,那麼深拷貝就很容易了解了。

深拷貝:建立一個新對象,然後将目前對象的非靜态字段複制到該新對象,無論該字段是值類型的還是引用類型,都複制獨立的一份。當你修改其中一個對象的任何内容時,都不會影響另一個對象的内容。

java中指派語句(=)與淺拷貝與深拷貝弄出來的對象有什麼差別

那麼該如何實作深拷貝呢?Object 類提供的 clone 是隻能實作 淺拷貝的。

4.3、如何實作深拷貝?

深拷貝的原理我們知道了,就是要讓原始對象和克隆之後的對象所具有的引用類型屬性不是指向同一塊堆記憶體,這裡有三種實作思路。

  • 讓每個引用類型屬性内部都重寫clone() 方法
  • 使用序列化流
  • 使用開源工具類

4.3.1、讓每個引用類型屬性内部都重寫clone() 方法

既然引用類型不能實作深拷貝,那麼我們将每個引用類型都拆分為基本類型,分别進行淺拷貝。比如上面的例子,Person 類有一個引用類型 Address(其實String 也是引用類型,但是String類型有點特殊,字元常量池),我們在 Address 類内部也重寫 clone 方法。如下:

Address.class:

package com.wlw.testjava8.testCopy;

public class Address implements Cloneable{

    private String provices;
    private String city;
    public void setAddress(String provices,String city){
        this.provices = provices;
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [provices=" + provices + ", city=" + city + "]";
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}      

然後更改 Person.class 的 clone() 方法:

@Override
    protected Object clone() throws CloneNotSupportedException {
        Person p = (Person)super.clone();
        p.address = (Address) address.clone();
        return p;
    }      

測試還是和上面一樣,我們會發現更改了p2對象的Address屬性,p1 對象的 Address 屬性并沒有變化。

//測試結果
p1:com.wlw.testjava8.testCopy.Person@610455d6
p1.getPname.hashCode: 117826
p2:com.wlw.testjava8.testCopy.Person@511d50c0
p2.getPname.hashCode: 117826
------------------------------------
位址修改之前:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
将複制之後的對象(p2)位址修改之後,對比變化:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]      

這種方法對于不能重寫的類,比如數組不适用!

但是這種做法有個弊端,這裡我們Person 類隻有一個 Address 引用類型,而Address類中沒有用到引用類型,是以我們隻用重寫 Address 類的clone 方法,但是如果 Address 類也存在一個引用類型,那麼我們也要重寫其clone 方法,這樣下去,有多少個引用類型,我們就要重寫多少次,如果存在很多引用類型,那麼代碼量顯然會很大,是以這種方法不太合适。

4.3.2、利用序列化

序列化是将對象寫到流中便于傳輸,而反序列化則是把對象從流中讀取出來。這裡寫到流中的對象則是原始對象的一個拷貝,因為原始對象還存在 JVM 中,是以我們可以利用對象的序列化産生克隆對象,然後通過反序列化擷取這個對象。

原理:

  • 其原理是:首先使要序列化的對象和該對象的引用類型成員變量對象的類都實作Serializable接口,将對象序列化到輸出流中,然後再反序列化為對象就完成了完全的複制操作了,反序列化對象類似于運作新對象的構造方法。一般序列化到外部檔案,此時隻需要克隆對象,并不需要外部檔案,是以我們的序列化和反序列化也應該在記憶體中進行最好,是以還使用到在記憶體操作資料的流ByteArrayOutputStream和ByteArrayInputStream,他們的輸出和讀取都預設是在記憶體的數組中操作。
  • 首先建立一個ByteArrayOutputStream記憶體數組輸出流,建立一個ObjectOutputStream序列化流,并傳入記憶體數組輸出流,使用序列化流的writeobject方法将要序列化的對象寫入内部數組中,然後建立一個ByteArrayInputStream記憶體數組讀取流,傳入一個讀取資料的數組,這個數組通過記憶體數組輸出流的toByteArray方法獲得,這個數組裡面的資料其實就是已經被序列化成二進制資料的對象。最後建立一個ObjectInputStream反序列化流,并傳入記憶體數組讀取流,使用反序列化流的readobject方法将數組中的對象的資訊,反序列化出來。反序列化出的對象就是一個新的對象,完成了深克隆。當然還可以固定要被序列化對象的版本号,定義一個private static final long serialVersionUID,但需要注意靜态的成員和transient關鍵字修飾的成員不能被序列化

注意每個需要序列化的類都要實作 Serializable 接口,如果有某個屬性不需要序列化,可以将其聲明為 transient,即将其排除在克隆屬性之外。

package com.wlw.testjava8.testCopy;

import java.io.Serializable;

public class Address2 implements Serializable {

    private String provices;
    private String city;
    public void setAddress(String provices, String city){
        this.provices = provices;
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [provices=" + provices + ", city=" + city + "]";
    }
}      
package com.wlw.testjava8.testCopy;

import java.io.Serializable;

public class Person2 implements Serializable {
    public String pname;
    public int page;
    public Address2 address;
    public Person2() {}

    public Person2(String pname, int page){
        this.pname = pname;
        this.page = page;
        this.address = new Address2();
    }

    public void setAddress(String provices,String city ){
        address.setAddress(provices, city);
    }
    public void display(String name){
        System.out.println(name+":"+"pname=" + pname + ", page=" + page +","+ address);
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }
}      
package com.wlw.testjava8.testCopy;

import java.io.*;

public class testCopy2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        Person2 p1 = new Person2("wlw",21);
        p1.setAddress("湖北省", "武漢市");

        //序列化
        //記憶體數組輸出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //序列化流
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        //将資料寫入序列化流中,随後會被傳遞到記憶體數組輸出流中,将對象序列化為byte[]類型的資料
        oos.writeObject(p1);

        //反序列化
        //從記憶體數組輸出流中擷取到p1的byte[]類型的資料,傳入記憶體數組輸入流
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        //将記憶體數組輸入流傳給反序列化流,這樣也實作了byte[]類型的資料的傳遞
        ObjectInputStream ois = new ObjectInputStream(bis);
        //使用readObject,從反序列化流中讀取資料,将byte[]類型的資料反序列化成Person2對象
        Person2 p2 = (Person2)ois.readObject();

        System.out.println("p1.hashCode: "+p1.hashCode());
        System.out.println("p2.hashCode: "+p2.hashCode());
        System.out.println("p1:"+p1);
        System.out.println("p1.getPname.hashCode: "+p1.getPname().hashCode());
        System.out.println("p2:"+p2);
        System.out.println("p2.getPname.hashCode: "+p2.getPname().hashCode());

        System.out.println("------------------------------------");
        System.out.println("位址修改之前:");
        p1.display("p1");
        p2.display("p2");
        p2.setAddress("湖北省", "荊州市");
        System.out.println("将複制之後的對象(p2)位址修改之後,對比變化:");
        p1.display("p1");
        p2.display("p2");
    }
}
//輸出結果:
p1.hashCode: 1581781576
p2.hashCode: 1534030866
p1:com.wlw.testjava8.testCopy.Person2@5e481248
p1.getPname.hashCode: 117826
p2:com.wlw.testjava8.testCopy.Person2@5b6f7412
p2.getPname.hashCode: 117826
------------------------------------
位址修改之前:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
将複制之後的對象(p2)位址修改之後,對比變化:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]      

因為序列化産生的是兩個完全獨立的對象,所有無論嵌套多少個引用類型,序列化都是能實作深拷貝的。

4.3.2、使用開源工具類

這裡介紹一下Json工具:Gson的使用。​​Gson的下載下傳​​

這裡複制粘貼出Address3、Person3,這兩個類什麼都沒繼承,裡面也隻有屬性和常用的方法(也沒有clone)

package com.wlw.testjava8.testCopy;

public class Address3 {

    private String provices;
    private String city;
    public void setAddress(String provices, String city){
        this.provices = provices;
        this.city = city;
    }
    @Override
    public String toString() {
        return "Address [provices=" + provices + ", city=" + city + "]";
    }
}      
package com.wlw.testjava8.testCopy;

import java.io.Serializable;

public class Person3 {
    public String pname;
    public int page;
    public Address3 address;
    public Person3() {}

    public Person3(String pname, int page){
        this.pname = pname;
        this.page = page;
        this.address = new Address3();
    }

    public void setAddress(String provices,String city ){
        address.setAddress(provices, city);
    }
    public void display(String name){
        System.out.println(name+":"+"pname=" + pname + ", page=" + page +","+ address);
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }
}      
package com.wlw.testjava8.testCopy;
import com.google.gson.Gson;

public class testCopy3 {
    public static void main(String[] args) {

        Person3 p1 = new Person3("wlw",21);
        p1.setAddress("湖北省", "武漢市");

        /*使用Gson工具*/
        Gson gson = new Gson();
        //将對象序列化為json字元串
        String p1Str = gson.toJson(p1);
        //然後将字元串反序列化為對象
        Person3 p2 = gson.fromJson(p1Str, Person3.class);

        System.out.println("p1.hashCode: "+p1.hashCode());
        System.out.println("p2.hashCode: "+p2.hashCode());
        System.out.println("p1:"+p1);
        System.out.println("p1.getPname.hashCode: "+p1.getPname().hashCode());
        System.out.println("p2:"+p2);
        System.out.println("p2.getPname.hashCode: "+p2.getPname().hashCode());

        System.out.println("------------------------------------");
        System.out.println("位址修改之前:");
        p1.display("p1");
        p2.display("p2");
        p2.setAddress("湖北省", "荊州市");
        System.out.println("将複制之後的對象(p2)位址修改之後,對比變化:");
        p1.display("p1");
        p2.display("p2");
    }
}
//輸出結果:
p1.hashCode: 1288354730
p2.hashCode: 1274370218
p1:com.wlw.testjava8.testCopy.Person3@4ccabbaa
p1.getPname.hashCode: 117826
p2:com.wlw.testjava8.testCopy.Person3@4bf558aa
p2.getPname.hashCode: 117826
------------------------------------
位址修改之前:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
将複制之後的對象(p2)位址修改之後,對比變化:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]      

5、指派語句(=)與淺拷貝與深拷貝弄出來的對象對比

建立一個Person對象:p1,利用語句(=)、淺拷貝、深拷貝分别根據p1得到三個p2,對比p1與三個p2的equals(),hashCode();【前提是:equals()方法沒用重寫】

equals():比較的是對象引用

hashCode():得到的是對象的hash值,hash值就是對象在哈希表中的索引位置(也是位址)

5.1、指派語句(=):

  • 一般使用​

    ​=​

    ​号做指派操作的時候,對于​

    ​基本資料類型​

    ​,實際上是​

    ​拷貝它的值​

    ​;對于​

    ​引用資料類型​

    ​而言,其實指派的隻是這個對象的引用,将原對象的引用傳遞過去,原對象和新的對象實際上還是​

    ​指向同一個對象​

    ​。
  • p1與p2的equals()相等,hashCode()相等
package com.wlw.testjava8.testCopy;

public class testEqualsAndHashCode {
    public static void main(String[] args) {

        Person3 p1 = new Person3("wlw",21);
        p1.setAddress("湖北省", "武漢市");

        Person3 p2 = p1;
        
        System.out.println("p1.equals(p2): "+p1.equals(p2));
        System.out.println("p1.hashCode: "+p1.hashCode());
        System.out.println("p2.hashCode: "+p2.hashCode());
        System.out.println("p1:"+p1);
        System.out.println("p2:"+p2);

        System.out.println("------------------------------------");
        System.out.println("位址修改之前:");
        p1.display("p1");
        p2.display("p2");
        p2.setAddress("湖北省", "荊州市");
        System.out.println("将複制之後的對象(p2)位址修改之後,對比變化:");
        p1.display("p1");
        p2.display("p2");
    }
}
//輸出結果:
p1.equals(p2): true
p1.hashCode: 1625635731
p2.hashCode: 1625635731
p1:com.wlw.testjava8.testCopy.Person3@60e53b93
p2:com.wlw.testjava8.testCopy.Person3@60e53b93
------------------------------------
位址修改之前:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
将複制之後的對象(p2)位址修改之後,對比變化:
p1:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]      

5.2、淺拷貝:

  • p1與p2的equals()不相等,hashCode()不相等
package com.wlw.testjava8.testCopy;

public class testCopy4 {
    public static void main(String[] args) throws CloneNotSupportedException {

        Person4 p1 = new Person4("wlw",21);
        p1.setAddress("湖北省", "武漢市");
        Person4 p2 = (Person4) p1.clone();

        System.out.println("p1.equals(p2): "+p1.equals(p2));
        System.out.println("p1.hashCode: "+p1.hashCode());
        System.out.println("p2.hashCode: "+p2.hashCode());
        System.out.println("p1:"+p1);
        System.out.println("p2:"+p2);

        System.out.println("------------------------------------");
        System.out.println("位址修改之前:");
        p1.display("p1");
        p2.display("p2");
        p2.setAddress("湖北省", "荊州市");
        System.out.println("将複制之後的對象(p2)位址修改之後,對比變化:");
        p1.display("p1");
        p2.display("p2");
    }
}
//輸出結果:
p1.equals(p2): false
p1.hashCode: 1625635731
p2.hashCode: 1580066828
p1:com.wlw.testjava8.testCopy.Person4@60e53b93
p2:com.wlw.testjava8.testCopy.Person4@5e2de80c
------------------------------------
位址修改之前:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
将複制之後的對象(p2)位址修改之後,對比變化:
p1:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]      

5.3、深拷貝:

  • p1與p2的equals()不相等,hashCode()不相等
package com.wlw.testjava8.testCopy;

import com.google.gson.Gson;

public class testCopy3 {
    public static void main(String[] args) {

        Person3 p1 = new Person3("wlw",21);
        p1.setAddress("湖北省", "武漢市");

        /*使用Gson工具*/
        Gson gson = new Gson();
        //将對象序列化為json字元串
        String p1Str = gson.toJson(p1);
        //然後将字元串反序列化為對象
        Person3 p2 = gson.fromJson(p1Str, Person3.class);

        System.out.println("p1.equals(p2): "+p1.equals(p2));
        System.out.println("p1.hashCode: "+p1.hashCode());
        System.out.println("p2.hashCode: "+p2.hashCode());
        System.out.println("p1:"+p1);
        System.out.println("p2:"+p2);

        System.out.println("------------------------------------");
        System.out.println("位址修改之前:");
        p1.display("p1");
        p2.display("p2");
        p2.setAddress("湖北省", "荊州市");
        System.out.println("将複制之後的對象(p2)位址修改之後,對比變化:");
        p1.display("p1");
        p2.display("p2");       
    }
}
//輸出結果:
p1.equals(p2): false
p1.hashCode: 1288354730
p2.hashCode: 1274370218
p1:com.wlw.testjava8.testCopy.Person3@4ccabbaa
p2:com.wlw.testjava8.testCopy.Person3@4bf558aa
------------------------------------
位址修改之前:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
将複制之後的對象(p2)位址修改之後,對比變化:
p1:pname=wlw, page=21,Address [provices=湖北省, city=武漢市]
p2:pname=wlw, page=21,Address [provices=湖北省, city=荊州市]      

5.4、總結一下:

指派語句(=):p1與p2的equals()相等,hashCode()相等

淺拷貝:p1與p2的equals()不相等,hashCode()不相等