Java的深淺拷貝你瞭解嗎?

1、概述

拷貝的一個經典的使用場景:當前對象要傳給其他多個方法使用,如果該對象在某一個方法中被修改,那麼這個修改會影響到其他方法。 如果要避免這種影響,就需要給每一個方法都傳入一個當前對象的拷貝。

深與淺拷貝的區別就在於對複雜對象的處理:對於基本類型,淺拷貝、深拷貝都是拷貝的值;對於引用類型淺拷貝的是對象的引用。而深拷貝則是直接新建一個對象實例。

 

注意淺拷貝中的複雜引用以及簡單引用:對於簡單引用,拷貝後的對象指向新的地址不會影響到原對象。複雜引用,拷貝後的對象將內部的對象指向新的地址,影響到原對象對應的內部對象。這裏一定理解下,抓住“地址”指向去理解就好了。

 

 

2、代碼實現

(1)淺拷貝實現方式:類實現Cloneable接口,並且重寫clone()方法。詳細見下面的代碼

(2)深拷貝:主要是針對類內部的複雜引用變量。兩種方式:1)複雜引用也實現Cloneable,並重寫clone()方法,然後在父對象重寫的clone方法中將該複雜引用指向克隆後的引用  2)直接將需要克隆的對象進行序列化,然後反序列化就可以得到一個深拷貝的對象。

當然這些工作都有現成的輪子了,藉助於Apache Commons工具類可以直接實現:

  • 淺克隆:BeanUtils.cloneBean(Object obj);
  • 深克隆:SerializationUtils.clone(T object);

當然可以直接使用

2.1淺拷貝

父子類

 1 @Data
 2 @Builder
 3 class Father implements Cloneable {
 4     Long age;
 5     StringBuilder name;
 6     Son son;
 7 
 8     @Override
 9     public Object clone() {
10         //淺拷貝
11         try {
12             return super.clone();
13         } catch (CloneNotSupportedException e) {
14             e.printStackTrace();
15             return null;
16         }
17     }
18 }
19 
20 @Data
21 @Builder
22 class Son {
23     Long age;
24     String name;
25     int grade;
26 }

 

 

初始賦值

1 public static Son son = Son.builder().age(new Long(30L)).name("大兒子").grade(100).build();
2 public static Father father = Father.builder().age(new Long(50L)).name("爸爸").son(son).build();

 

淺拷貝示例:

  private static void shallowClone() {
        System.out.println("父親年齡:" + father.getAge());
        System.out.println("父親姓名:" + father.getName());
        System.out.println("兒子年齡:" + father.getSon().getAge());
        System.out.println("兒子姓名:" + father.getSon().getName());
        System.out.println("兒子分數:" + father.getSon().getGrade());
        //開始克隆

        Father fatherInLaw = (Father) father.clone();
        fatherInLaw.getSon().setAge(10L);
        fatherInLaw.getSon().setGrade(0);
        fatherInLaw.getSon().setName("繼子");
        fatherInLaw.setAge(new Long(80L));
        fatherInLaw.setName("繼父");

        //修改後結果
        System.out.println("==========淺拷貝後===========");

        System.out.println("父親年齡:" + father.getAge());
        System.out.println("父親姓名:" + father.getName());
        System.out.println("兒子年齡:" + father.getSon().getAge());
        System.out.println("兒子姓名:" + father.getSon().getName());
        System.out.println("兒子分數:" + father.getSon().getGrade());
    }

結果輸出

父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
==========淺拷貝後===========
父親年齡:50
父親姓名:爸爸
兒子年齡:10
兒子姓名:繼子
兒子分數:0

 

2.2.深拷貝

(1)子引用重寫clone方法,並且在父類中改變子引用的指向

父子類

@Data
@Builder
class Father implements Cloneable {
    Long age;
    StringBuilder name;
    Son son;

    @Override
    public Object clone() {
        Father father = null;
        //淺拷貝
        try {
            father = (Father) super.clone();
            father.son = (Son) son.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
        return father;
    }
}

@Data
@Builder
class Son implements Cloneable {
    Long age;
    String name;
    int grade;

    @Override
    public Object clone() {
        //拷貝
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

View Code

賦初值

1  public static Son son = Son.builder().age(new Long(30L)).name("大兒子").grade(100).build();
2     public static Father father = Father.builder().age(new Long(50)).name(new StringBuilder("爸爸")).son(son).build();

深拷貝示例:

//深拷貝
    private static void deepClone() {
        System.out.println("父親年齡:" + father.getAge());
        System.out.println("父親姓名:" + father.getName());
        System.out.println("兒子年齡:" + father.getSon().getAge());
        System.out.println("兒子姓名:" + father.getSon().getName());
        System.out.println("兒子分數:" + father.getSon().getGrade());
        //開始克隆

        Father fatherInLaw = (Father) father.clone();
        fatherInLaw.getSon().setAge(10L);
        fatherInLaw.getSon().setGrade(0);
        fatherInLaw.getSon().setName("繼子");
        fatherInLaw.setAge(new Long(80L));
        fatherInLaw.setName(new StringBuilder("繼父"));

        //修改前
        System.out.println("==========淺拷貝後===========");

        System.out.println("父親年齡:" + father.getAge());
        System.out.println("父親姓名:" + father.getName());
        System.out.println("兒子年齡:" + father.getSon().getAge());
        System.out.println("兒子姓名:" + father.getSon().getName());
        System.out.println("兒子分數:" + father.getSon().getGrade());
    }

結果

父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
==========淺拷貝後===========
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100

(2)序列化方式

父子類

@Data
@Builder
class Father implements Serializable {
    Long age;
    StringBuilder name;
    Son son;

    public Object deepClone() throws IOException, ClassNotFoundException {
        //序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return ois.readObject();
    }
}

@Data
@Builder
class Son implements Serializable {
    Long age;
    String name;
    int grade;
}

View Code

實現方法

 //深拷貝
    private static void deepCloneV2() throws IOException, ClassNotFoundException {
        System.out.println("父親年齡:" + father.getAge());
        System.out.println("父親姓名:" + father.getName());
        System.out.println("兒子年齡:" + father.getSon().getAge());
        System.out.println("兒子姓名:" + father.getSon().getName());
        System.out.println("兒子分數:" + father.getSon().getGrade());
        //開始克隆

        Father fatherInLaw = (Father) father.deepClone();
        fatherInLaw.getSon().setAge(10L);
        fatherInLaw.getSon().setGrade(0);
        fatherInLaw.getSon().setName("繼子");
        fatherInLaw.setAge(new Long(80L));
        fatherInLaw.setName(new StringBuilder("繼父"));

        //修改前
        System.out.println("==========淺拷貝後===========");

        System.out.println("父親年齡:" + father.getAge());
        System.out.println("父親姓名:" + father.getName());
        System.out.println("兒子年齡:" + father.getSon().getAge());
        System.out.println("兒子姓名:" + father.getSon().getName());
        System.out.println("兒子分數:" + father.getSon().getGrade());
    }

結果:

父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
==========淺拷貝後===========
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100

3、使用Apach  Commons

3.1實現深拷貝,需要實現序列化接口,SerializationUtils.clone(T object);

父子類

@Data
@Builder
class Father implements Serializable{
    Long age;
    StringBuilder name;
    Son son;
}

@Data
@Builder
class Son implements Serializable{
    Long age;
    String name;
    int grade;
}

View Code

實現方法

 //深拷貝
    private static void deepCloneCommons() throws IOException, ClassNotFoundException {
        System.out.println("父親年齡:" + father.getAge());
        System.out.println("父親姓名:" + father.getName());
        System.out.println("兒子年齡:" + father.getSon().getAge());
        System.out.println("兒子姓名:" + father.getSon().getName());
        System.out.println("兒子分數:" + father.getSon().getGrade());
        //開始克隆

        Father fatherInLaw = (Father) SerializationUtils.clone(father);
        fatherInLaw.getSon().setAge(10L);
        fatherInLaw.getSon().setGrade(0);
        fatherInLaw.getSon().setName("繼子");
        fatherInLaw.setAge(new Long(80L));
        fatherInLaw.setName(new StringBuilder("繼父"));

        //修改前
        System.out.println("==========淺拷貝後===========");

        System.out.println("父親年齡:" + father.getAge());
        System.out.println("父親姓名:" + father.getName());
        System.out.println("兒子年齡:" + father.getSon().getAge());
        System.out.println("兒子姓名:" + father.getSon().getName());
        System.out.println("兒子分數:" + father.getSon().getGrade());
    }

View Code

結果

父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
==========深拷貝後===========
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100

3.2使用工具類實現深拷貝,BeanUtils.copyProperties(Object dest, Object orig)直接調用即可實現淺拷貝,BeanUtils.cloneBean(Object obj)最終也是調用的copyProperties方法。

 

摘自:http://www.manongjc.com/detail/9-vvtzsyvyjxehgdw.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章