由 System.arraycopy 引發的鞏固:物件引用 與 物件 的區別

林冠巨集發表於2017-10-11

作者:林冠巨集 / 指尖下的幽靈

掘金:juejin.im/user/587f0d…

部落格:www.cnblogs.com/linguanh/

GitHub : github.com/af913337456…

聯絡方式 / Contact:913337456@qq.com


瞭解這些術語:

  • 深複製又稱深拷貝,兩個變數的記憶體地址不一樣,各自修改不影響對方。
  • 淺複製又稱淺拷貝,兩個變數的記憶體地址一樣,既是同一個變數,僅僅是引用不同罷了,各自修改是會影響對方的,因為本身就是同一個。

這篇文文我要講的有:

  • System.arraycopy 是深複製
  • System.arraycopy 的陷阱點
  • 物件引用物件 的區別
  • 簡歷不要寫 精通java,寫 熟練

首先明確一點,System.arraycopy 操作的是陣列,效果是深複製。 是不是覺得怎麼和你印象的中不一樣?
重點來了,對於物件陣列,例如: User[],這種陣列,有一個注意點,這個點就是:對於陣列內的物件是淺拷貝。

一句話:

  • System.arraycopy 對於陣列是深拷貝,對於陣列內的物件是淺拷貝。因為操作的傳入引數是陣列,那麼迴歸本意,效果是深複製。

上面的含義一定要區分清楚!
因為現在網上很多觀點是混淆,亂JB寫的
因為現在網上很多觀點是混淆,亂JB寫的
因為現在網上很多觀點是混淆,亂JB寫的

來看個例子,下面的程式碼可以自己去執行驗證。已經充分驗證了上面我的觀點。

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

        User[] users=new User[]{
                new User("111"),new User("222"),new User("333")
        };//初始化物件陣列
        User[] target=new User[users.length];//新建一個目標物件陣列

        System.arraycopy(users, 0, target, 0, users.length); // 複製

        System.out.println("陣列地址是否一樣:"+(users == target?"淺複製":"深複製"));
        System.out.println("陣列內物件地址是否一樣:"+(users[0] == target[0]?"淺複製":"深複製"));

        target[0].setEmail("444");
        System.out.println("修改後輸出 users[0] ,是否和 target[0]一樣是444,users[0]:"+users[0].getEmail());

        users[0] = null; // -----------------①
        System.out.println("遍歷 users");
        for (User user : users){
            System.out.println(user);
        }
        System.out.println("遍歷 target");
        for (User user : target){
            System.out.println(user);
        }

        users = null;
        if(target == null){
            System.out.println("users = null 後是否 target 也變成了 null,是則證明是淺複製");
        }else{
            System.out.println("users = null 後是否 target 不受任何影響,是則再次證明陣列是深複製");
        }
    }
}
class User{
    private String email;
    public User(String email) { this.email = email; }
    public String getEmail() { return email;}
    public void setEmail(String email) { this.email = email; }
    @Override
    public String toString() { return "User [email=" + email+ "]"; }
}複製程式碼

輸出的結果如下:

陣列地址是否一樣:深複製
陣列內物件地址是否一樣:淺複製
修改後輸出 users[0],是否和 target[0]一樣是444,users[0]:444
遍歷 users
null
User [email=222]
User [email=333]
遍歷 target
User [email=444]
User [email=222]
User [email=333]
users = null 後是否 target 不受任何影響,是則再次證明陣列是深複製複製程式碼

上面我的例子還留有一個經典的面試點,既是標號 ① 對應的行:

users[0] = null; // -----------------①複製程式碼

上面我們談到了,陣列內的物件是淺複製,那麼在上面這行我把 users[0] 下標為0的物件弄為 null 後。後面再遍歷輸出:

System.out.println("遍歷 users");
for (User user : users){
    System.out.println(user);
}
System.out.println("遍歷 target");
for (User user : target){
    System.out.println(user);
}複製程式碼

明顯地,我們可以容易知道,user[0] 此時輸出的肯定是 null。那麼 target[0] 呢?淺拷貝的話,target[0] 必然在記憶體地址和值上面全等於 users[0]

但是從 System.out.println("遍歷 target"); 的結果來看。卻發現 target 輸出的是:

遍歷 target
User [email=444]  // 第一個不是 null
User [email=222]
User [email=333]複製程式碼

這是為什麼呢?其實這是最為基礎的: 物件引用與物件的區別,一名合格,僅僅是合格的 Java 語言使用者,這個得知道。下面我們來談談它。

有一個類:

public class Demo {  
    //預設構造方法  
    public Demo{  
    }  
}複製程式碼

我們用它建立個物件

Demo fuck = new Demo();複製程式碼

這一條語句,其實包括了四個動作:

  • 右邊的“new Demo”,是以Demo類為模板,在堆空間裡建立一個Demo物件。
  • 末尾的()意味著,在物件建立後,立即呼叫Demo類的建構函式,對剛生成的物件進行初始化。
  • 左邊的“Demo fuck”建立了一個Demo類引用變數,它存放在棧空間中。也就是用來指向Demo物件的物件引用。
  • “=”操作符使物件引用指向剛建立的那個Demo物件。物件引用的名字叫做 fuck
Demo fuck;//一個物件引用  
fuck = new Demo();//一個物件引用指向一個物件複製程式碼

一個物件可以被多個物件引用同時引用。它們操作的始終是同一個。

Demo fuck,fuck2;//建立多個物件引用  
fuck = new Demo();  
fuck2 = fuck;複製程式碼

好了,回答之前的坑, users[0] = new User("111")users[0] = nulltarget[0] = users[0],users[0] = null 只是把棧中的 users[0] 物件引用弄為了 null,物件 new User("111") 與它無關,自然沒影響,target[0] 是另外一個物件引用,也是指向了 new User("111")。

根據 大 Jvm 的 記憶體回收演算法之根搜尋,引用鏈存在、強引用、when 當前應用記憶體不夠了,強制丟擲 OOM。那麼當 target[0] = nullnew User("111") 對應的這塊記憶體就會進入被回收的佇列中,“死去”。

最後這段是不是有點看不懂 ?那證明你要繼續努力了。

相關文章