作者:林冠巨集 / 指尖下的幽靈
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] = 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")
對應的這塊記憶體就會進入被回收的佇列中,“死去”。
最後這段是不是有點看不懂 ?那證明你要繼續努力了。