拷貝對象是java中常常會遇到的問題。java中存在兩種類型,基礎類型和引用類型。java
java的賦值都是傳值的,對於基礎類型來講,會拷貝具體的內容,可是對於引用對象來講,存儲的這個值只是指向實際對象的地址,拷貝也只會拷貝引用地址。git
由於引用對象的存在,因此常常會出現和預期不同的狀況。github
本文將會深刻的探討一下在拷貝對象中會出現的淺拷貝和深拷貝的狀況。數組
java中全部的對象都是繼承自java.lang.Object。Object對象中提供了一個clone方法,來供咱們對java對象進行拷貝。ide
protected native Object clone() throws CloneNotSupportedException;
複製代碼
這個clone方法是native的,因此不須要咱們來實現,可是注意clone方法仍是protected,這意味着clone方法只能在java.lang包或者其子類可見。函數
若是咱們想要在一個程序中調用某個對象的clone方法則是不能夠的。由於clone方法是定義在Object中的,該對象並無對外可見的clone方法。測試
JDK的建議是讓咱們去實現接口Cloneable,實現了這個接口就表示這個對象能夠調用Object的clone方法。this
注意,即便你實現了Cloneable接口,仍是沒法在外部程序中調用該對象的clone方法:spa
public interface Cloneable {
}
複製代碼
由於Cloneable是空的,明沒有強制要你去實現clone方法。設計
這是JDK在設計上的問題,致使clone方法並不像預期那麼好用。
首先clone只是對象的拷貝,它只是簡單的拷貝對象,而不會去執行對象的構造函數。
其次clone會致使淺拷貝的問題。
咱們舉個clone產生的淺拷貝的例子,咱們定義一個對象中的對象,而後嘗試拷貝:
@Data
public class Address implements Cloneable{
private String name;
//不是好的方式
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
複製代碼
@Data
public class CustUser implements Cloneable{
private String firstName;
private String lastName;
private Address address;
private String[] cars;
@Override
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
複製代碼
上面的例子中,咱們定義了CustUser和Address。
public void testShallowCopy() throws CloneNotSupportedException {
Address address= new Address();
address.setName("北京天安門");
CustUser custUser = new CustUser();
custUser.setAddress(address);
custUser.setLastName("李");
custUser.setFirstName("雷");
String[] cars = new String[]{"別克","路虎"};
custUser.setCars(cars);
CustUser custUserCopy=(CustUser) custUser.clone();
custUserCopy.setFirstName("梅梅");
custUserCopy.setLastName("韓");
custUserCopy.getAddress().setName("北京頤和園");
custUserCopy.getCars()[0]="奧迪";
log.info("{}",custUser);
log.info("{}",custUserCopy);
}
複製代碼
淺拷貝咱們只調用了CustUser的clone方法。看下輸出結果:
CustUser(firstName=雷, lastName=李, address=Address(name=北京頤和園), cars=[奧迪, 路虎])
CustUser(firstName=梅梅, lastName=韓, address=Address(name=北京頤和園), cars=[奧迪, 路虎])
複製代碼
咱們能夠看到拷貝以後的Address變化會影響到被拷貝的對象。
上面的例子咱們還要關注兩個點:第一點String是不可變的。無論是拷貝仍是賦值,String都是不可變的。
第二點,上面的例子中咱們定義了一個數組,能夠看到若是隻是調用clone的話,數組也是淺拷貝。
要使用深拷貝,只須要修改CustUser的構造函數就能夠了:
//不是很好的使用方式
@Override
public Object clone() throws CloneNotSupportedException{
CustUserDeep custUserDeep=(CustUserDeep)super.clone();
custUserDeep.address=(Address)address.clone();
custUserDeep.cars=cars.clone();
return custUserDeep;
}
複製代碼
在重寫的clone方法中,咱們分別調用了CustUser,Address和數組的clone方法來進行拷貝。
再運行一次上面的測試代碼:
CustUserDeep(firstName=雷, lastName=李, address=Address(name=北京天安門), cars=[別克, 路虎])
CustUserDeep(firstName=梅梅, lastName=韓, address=Address(name=北京頤和園), cars=[奧迪, 路虎])
複製代碼
能夠看到address和cars是不一樣的,這表示咱們的深拷貝是成功的。
上面的例子咱們是經過overridden Object的clone方法來實現的。
可是最佳實踐是不要overridden clone。那咱們怎麼作呢?
使用構造函數來構建新的對象:
//好的方式
Address(Address address){
this.name=address.name;
}
複製代碼
//很好的方式
CustUserDeep(CustUserDeep custUserDeep){
this.firstName=custUserDeep.firstName;
this.lastName=custUserDeep.lastName;
this.cars=custUserDeep.getCars().clone();
this.address=new Address(custUserDeep.getAddress());
}
複製代碼
聽說數組直接用clone來拷貝會更快,也可使用下面的方式來拷貝數組:
this.cars= Arrays.copyOf(custUserDeep.getCars(),custUserDeep.getCars().length);
複製代碼
本文講解了淺拷貝和深拷貝的應用,並對clone方法作了深刻的探討。
本文做者:flydean程序那些事
本文連接:www.flydean.com/java-base-s…
本文來源:flydean的博客
歡迎關注個人公衆號:程序那些事,更多精彩等着您!