1.首先,你要知道怎么實(shí)現(xiàn)克?。簩?shí)現(xiàn)Cloneable接口,在bean里面重寫clone()方法,權(quán)限為public。
2.其次,你要大概知道什么是地址傳遞,什么是值傳遞。
3.最后,你要知道你為什么使用這個(gè)clone方法。
先看第一條,簡(jiǎn)單的克隆代碼的實(shí)現(xiàn)。這個(gè)也就是我們?cè)跊]了解清楚這個(gè)Java的clone的時(shí)候,會(huì)出現(xiàn)的問題。
看完代碼,我再說明這個(gè)時(shí)候的問題。
先看我要克隆的學(xué)生bean的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package com.lxk.model; /** * 學(xué)生類:有2個(gè)屬性:1,基本屬性-String-name;2,引用類型-Car-car。 * <p> * Created by lxk on 2017/3/23 */ public class Student implements Cloneable { private String name; private Car car; public String getName() { return name; } public void setName(String name) { this .name = name; } public Car getCar() { return car; } public void setCar(Car car) { this .car = car; } @Override public String toString() { return "Student{" + "name='" + name + '\ '' + ", car=" + car + '}' ; } @Override public Student clone() { Student student = null ; try { student = (Student) super .clone(); } catch (CloneNotSupportedException ignored) { System.out.println(ignored.getMessage()); } return student; } } |
學(xué)生內(nèi)部引用了Car這個(gè)bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package com.lxk.model; import java.util.List; public class Car implements Comparable<Car> { private String sign; private int price; private List<Dog> myDog; private List<String> boys; public Car() { } public Car(String sign, int price) { this .sign = sign; this .price = price; } public Car(String sign, int price, List<Dog> myDog) { this .sign = sign; this .price = price; this .myDog = myDog; } public Car(String sign, int price, List<Dog> myDog, List<String> boys) { this .sign = sign; this .price = price; this .myDog = myDog; this .boys = boys; } public String getSign() { return sign; } public void setSign(String sign) { this .sign = sign; } public int getPrice() { return price; } public void setPrice( int price) { this .price = price; } public List<Dog> getMyDog() { return myDog; } public void setMyDog(List<Dog> myDog) { this .myDog = myDog; } public List<String> getBoys() { return boys; } public void setBoys(List<String> boys) { this .boys = boys; } @Override public int compareTo(Car o) { //同理也可以根據(jù)sign屬性排序,就不舉例啦。 return this .getPrice() - o.getPrice(); } @Override public String toString() { return "Car{" + "sign='" + sign + '\ '' + ", price=" + price + ", myDog=" + myDog + ", boys=" + boys + '}' ; } } |
最后就是main測(cè)試類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.lxk.findBugs; import com.lxk.model.Car; import com.lxk.model.Student; /** * 引用傳遞也就是地址傳遞需要注意的地方,引起的bug * <p> * Created by lxk on 2017/3/23 */ public class Bug2 { public static void main(String[] args) { Student student1 = new Student(); Car car = new Car( "oooo" , 100 ); student1.setCar(car); student1.setName( "lxk" ); //克隆完之后,student1和student2應(yīng)該沒關(guān)系的,修改student1不影響student2的值,但是完之后發(fā)現(xiàn),你修改car的值,student2也受影響啦。 Student student2 = student1.clone(); System.out.println( "學(xué)生2:" + student2); //先輸出student2剛剛克隆完之后的值,然后在修改student1的相關(guān)引用類型的屬性值(car)和基本屬性值(name) car.setSign( "X5" ); student1.setName( "xxx" ); System.out.println( "學(xué)生2:" + student2); //再次輸出看修改的結(jié)果 } } |
之后就該是執(zhí)行的結(jié)果圖了:
對(duì)上面執(zhí)行結(jié)果的疑惑,以及解釋說明:
我們可能覺得自己在bean里面實(shí)現(xiàn)clone接口,重寫了這個(gè)clone方法,那么學(xué)生2是經(jīng)由學(xué)生1clone,復(fù)制出來的,
那么學(xué)生1和學(xué)生2,應(yīng)該是毫不相干的,各自是各自,然后,在修改學(xué)生1的時(shí)候,學(xué)生2是不會(huì)受影響的。
但是結(jié)果,不盡人意。從上圖執(zhí)行結(jié)果可以看出來,除了名字,這個(gè)屬性是沒有被學(xué)生1影響,關(guān)于car的sign屬性已經(jīng)因?yàn)閷W(xué)生1的變化而變化,這不是我希望的結(jié)果。
可見,這個(gè)簡(jiǎn)單的克隆實(shí)現(xiàn)也僅僅是個(gè)“淺克隆”,也就是基本類型數(shù)據(jù),他是會(huì)給你重新復(fù)制一份新的,但是引用類型的,他就不會(huì)重新復(fù)制份新的。引用類型包括,上面的其他bean的引用,list集合,等一些引用類型。
那么怎么實(shí)現(xiàn)深克隆呢?
對(duì)上述代碼稍作修改,如下:
學(xué)生bean的clone重寫方法如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Override public Student clone() { Student student = null ; try { student = (Student) super .clone(); if (car != null ) { student.setCar(car.clone()); } } catch (CloneNotSupportedException ignored) { System.out.println(ignored.getMessage()); } return student; } |
然后還要Car類實(shí)現(xiàn)cloneable接口,復(fù)寫clone方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Override public Car clone() { Car car = null ; try { car = (Car) super .clone(); if (myDog != null ) { car.setMyDog(Lists.newArrayList(myDog)); } if (boys != null ) { car.setBoys(Lists.newArrayList(boys)); } } catch (CloneNotSupportedException ignored) { System.out.println(ignored.getMessage()); } return car; } |
主測(cè)試代碼不動(dòng),這個(gè)時(shí)候的執(zhí)行結(jié)果如下:
可以看到,這個(gè)時(shí)候,你再修改學(xué)生1的值,就不會(huì)影響到學(xué)生2的值,這才是真正的克隆,也就是所謂的深克隆。
怎么舉一反三?
可以看到,這個(gè)例子里面的引用類型就一個(gè)Car類型的屬性,但是實(shí)際開發(fā)中,除了這個(gè)引用其他bean類型的屬性外,可能還要list類型的屬性值用的最多。
那么要怎么深克隆呢,就像我在Car bean類里面做的那樣,把所有的引用類型的屬性,都在clone一遍。那么你在最上層調(diào)用這個(gè)clone方法的時(shí)候,他就是真的深克隆啦。
我代碼里面那么判斷是為了避免空指針異常。當(dāng)然,這個(gè)你也得注意咯。
注意 重寫clone方法的時(shí)候,里面各個(gè)屬性的null的判斷哦。
上面的是override clone()
方法來實(shí)現(xiàn)深克隆的。如果你這個(gè)要克隆的對(duì)象很復(fù)雜的話,你就不得不去每個(gè)引用到的對(duì)象去復(fù)寫這個(gè)clone方法,這個(gè)太啰嗦來,改的地方,太多啦。
還有個(gè)方法就是使用序列化來實(shí)現(xiàn)這個(gè)深拷貝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | /** * 對(duì)象的深度克隆,此處的對(duì)象涉及Collection接口和Map接口下對(duì)象的深度克隆 * 利用序列化和反序列化的方式進(jìn)行深度克隆對(duì)象 * * @param object 待克隆的對(duì)象 * @param <T> 待克隆對(duì)象的數(shù)據(jù)類型 * @return 已經(jīng)深度克隆過的對(duì)象 */ public static <T extends Serializable> T deepCloneObject(T object) { T deepClone = null ; ByteArrayOutputStream baos = null ; ObjectOutputStream oos = null ; ByteArrayInputStream bais = null ; ObjectInputStream ois = null ; try { baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); oos.writeObject(object); bais = new ByteArrayInputStream(baos .toByteArray()); ois = new ObjectInputStream(bais); deepClone = (T)ois.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (baos != null ) { baos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (oos != null ) { oos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (bais != null ) { bais.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (ois != null ) { ois.close(); } } catch (IOException e) { e.printStackTrace(); } } return deepClone; } |
具體的使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * 使用序列化來實(shí)現(xiàn)深拷貝簡(jiǎn)單。但是,所涉及到的所有對(duì)象都的實(shí)現(xiàn)序列化接口。 */ private static void cloneBySerializable() { Student student1 = new Student(); Car car = new Car( "oooo" , 100 , Lists.newArrayList( new Dog( "aaa" , true , true ))); student1.setCar(car); student1.setName( "lxk" ); Student student2 = deepCloneObject(student1); System.out.println( "學(xué)生2:" + student2); car.setSign( "X5" ); car.setMyDog( null ); student1.setName( "xxx" ); System.out.println( "學(xué)生2:" + student2); } |
實(shí)現(xiàn)的效果,還是和上面的一樣的,但是這個(gè)就簡(jiǎn)單多來,只需要給涉及到的每個(gè)引用類型,都去實(shí)現(xiàn)序列化接口就好啦。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
原文鏈接:https://blog.csdn.net/qq_27093465/article/details/65443355
微信公眾號(hào)搜索 “ 腳本之家 ” ,選擇關(guān)注
程序猿的那些事、送書等活動(dòng)等著你
聯(lián)系客服