由 System.arraycopy 引發的鞏固:對象引用 與 對象 的區別


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

掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8

博客:http://www.cnblogs.com/linguanh/

GitHub : https://github.com/af913337456/

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


了解這些術語:

  • 深復制又稱深拷貝,兩個變量的內存地址不一樣,各自修改不影響對方。
  • 淺復制又稱淺拷貝,兩個變量的內存地址一樣,既是同一個變量,僅僅是引用不同罷了,各自修改是會影響對方的,因為本身就是同一個。

這篇文文我要講的有:

  • System.arraycopy 是深復制
  • System.arraycopy 的陷阱點
  • 對象引用對象 的區別
  • 簡歷不要寫 精通java,寫 熟練

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

一句話:

  • System.arraycopy 對於數組是深拷貝,對於數組內的對象是淺拷貝。因為操作的傳入參數是數組,那么回歸本意,效果是深復制。

上面的含義一定要區分清楚! 因為現在網上很多觀點是混淆,亂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") 對應的這塊內存就會進入被回收的隊列中,“死去”。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM