作者:林冠宏 / 指尖下的幽靈
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] = null
,target[0] = users[0]
,users[0] = null 只是把棧中的 users[0] 對象引用弄為了 null,對象 new User("111") 與它無關,自然沒影響,target[0] 是另外一個對象引用,也是指向了 new User("111")。
根據 大 Jvm 的 內存回收算法之根搜索,引用鏈存在、強引用、when 當前應用內存不夠了,強制拋出 OOM。那么當 target[0] = null
,new User("111")
對應的這塊內存就會進入被回收的隊列中,“死去”。
最后這段是不是有點看不懂 ?那證明你要繼續努力了。