淺談關於java中的深淺拷貝

來源:互聯網
上載者:User

標籤:位元組   png   選擇   throw   分享圖片   ali   throws   兩種   setname   

一.淺拷貝(shallow copy)

 

 

1.如何?淺拷貝?

Object類 是所有類的直接或間接父類,Object中存在clone方法,如下

protected native Object clone() throws CloneNotSupportedException;

如果想要使一個類的對象能夠調用clone方法 ,則需要實現Cloneable介面, 並重寫 clone方法:

public class Student implements Cloneable{    private int sno ;    private String name;    //getter ,setter 省略    @Override    public Object clone() throws CloneNotSupportedException {        Student s = null;        try{            s = (Student)super.clone();        }catch (Exception e){            e.printStackTrace();        }        return s;    }}

 

現在測試clone方法:

@Test    public void test04() throws CloneNotSupportedException {        //建立Student對象        Student s1 = new Student();        s1.setSno(1);        s1.setName("Rye");        //通過clone 拷貝一個對象        Student s2 = (Student)s1.clone();        System.out.println("s1:"+s1);        System.out.println("s2:"+s2);        System.out.println("s1 == s2 ? ==> "+(s1 == s2));    }

按照預期,複製出的對象s2中的欄位值應該與s1相同,但與s1對應的對象不在同一塊記憶體空間,結果如下:

s1:Student{sno=1, name=‘Rye‘}s2:Student{sno=1, name=‘Rye‘}s1 == s2 ? ==> false
View Code

此時如果修改 s1中的sno為2,那麼會不會影響到s2中的sno呢?

//修改s1中的snos1.setSno(2);

結果如下:

s1:Student{sno=2, name=‘Rye‘}s2:Student{sno=1, name=‘Rye‘}
View Code

 

此時看似已經完成了 copy, s1 與 s2有著自己不同的值,但如果為Student中新增了Teacher類型的成員變數,結果還是跟上面一樣嗎?讓我們改造下代碼:

public class Teacher {    private int tno;    private String name;      //getter setter省略...  }
public class Student  implements Cloneable{    private int sno ;    private String name;    private Teacher teacher;    //getter ,setter ,toString 省略...    @Override    public Object clone() throws CloneNotSupportedException {        Student s = null;        try{            s = (Student)super.clone();        }catch (Exception e){            e.printStackTrace();        }        return s;    }    }

 

此時測試代碼如下:

    @Test    public void test02() throws CloneNotSupportedException {        Student student1 = new Student();        student1.setSno(1);        student1.setName("Rye");        Teacher teacher = new Teacher();        teacher.setTno(1);        teacher.setName("LinTong");        student1.setTeacher(teacher);        Student student2 = (Student)student1.clone();        System.out.println("student1:"+student1);        System.out.println("student2:"+student2);        System.out.println("student1 == student2 ? ==> "+ (student1 ==student2));        System.out.println("student1.teacher == student2.teacher ? ==> "+ (student1.getTeacher() ==student2.getTeacher()));    }

 

運行結果如下:

student1:Student{sno=1, name=‘Rye‘, teacher=Teacher{tno=1, name=‘LinTong‘}}student2:Student{sno=1, name=‘Rye‘, teacher=Teacher{tno=1, name=‘LinTong‘}}student1 == student2 ? ==> falsestudent1.teacher == student2.teacher ? ==> true
View Code

 

由此可見,此時經過clone產生的student2, 與 student1.二者中的teacher欄位, 指向同一塊記憶體空間;

那麼可能會有人問,這會有什麼影響嗎? 

我們拷貝的目的,更多的時候是希望獲得全新並且值相同的對象,操作原始對象或拷貝的新對象,對彼此之間互不影響;

此時我們修改student1中teacher的tno ,如下:

//修改teacher中的 tno值為2student1.getTeacher().setTno(2);

再次運行test:

student1:Student{sno=1, name=‘Rye‘, teacher=Teacher{tno=2, name=‘LinTong‘}}student2:Student{sno=1, name=‘Rye‘, teacher=Teacher{tno=2, name=‘LinTong‘}}student1 == student2 ? ==> falsestudent1.teacher == student2.teacher ? ==> true
View Code

 

此時發現,student2中的teacher的tno ,也跟著變化了.

變化的原因是:通過student1執行clone時,基本類型會完全copy一份到student2對應對象記憶體空間中, 但是對於Teacher對象僅僅是copy了一份Teacher的引用而已.

而student1 與 student2的引用 指向的是同一塊堆記憶體,因此不論是通過student1或是student2修改teacher 都會影響另外一個;

通過圖會更直觀一些:

 

 

2.淺拷貝中參考型別的變數拷貝的是對象的引用 , 可通過如下思路解決:

Teacher類中也覆寫clone方法:

    @Override    protected Object clone() {        Teacher teacher = null;        try {            teacher = (Teacher)super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return teacher;    }

 

修改Student中的clone方法,如下:

    @Override    public Object clone() {        Student s = null;        try{            s = (Student)super.clone();            Teacher t = (Teacher)this.teacher.clone();            s.setTeacher(t);        }catch (Exception e){            e.printStackTrace();        }        return s;    }

 

此時再次運行test:

student1:Student{sno=1, name=‘Rye‘, teacher=Teacher{tno=2, name=‘LinTong‘}}student2:Student{sno=1, name=‘Rye‘, teacher=Teacher{tno=1, name=‘LinTong‘}}student1 == student2 ? ==> falsestudent1.teacher == student2.teacher ? ==> false
View Code

 

由此可見,在copy Student的同時 將Teacher也進行了修改,:

目前來看是滿足了我們的需求,但是如果Teacher類中,同樣也有別的參考型別 的成員變數呢?

那麼就同樣需要一直覆寫clone方法,如果這個關係不是特多還可以接受,如果參考關聯性很複雜就會顯得代碼繁瑣;

此時應該使用序列化完成深度拷貝;

 

 

二.深拷貝(deep copy)

 

使用序列化完成深拷貝

深拷貝是利用物件流程,將對象序列化,再還原序列化得出新的對象. 因此首先需要實現序列化介面,如下:

public class Student implements Serializable{    private static final long serialVersionUID = -2232725257771333130L;    private int sno ;    private String name;    private Teacher teacher;
  //getter ,setter,toString()省略...}

Teacher也要實現序列化介面:

public class Teacher implements Serializable{    private static final long serialVersionUID = 4477679176385287943L;    private int tno;    private String name;
  
 //getter ,setter,toString()省略...
}

 

工具方法:

  //工具方法  public Object cloneObject(Object object) throws IOException, ClassNotFoundException {        //將對象序列化        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);        objectOutputStream.writeObject(object);        //將位元組還原序列化        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);        Object obj = objectInputStream.readObject();        return obj;    }

 

測試類別:

   public void test05() throws IOException, ClassNotFoundException {        Student student1 = new Student();        student1.setSno(1);        student1.setName("Rye");        Teacher teacher = new Teacher();        teacher.setTno(1);        teacher.setName("LinTong");        student1.setTeacher(teacher);        Student student2 = (Student)cloneObject(student1);        //修改teacher中的 tno值為2        student1.getTeacher().setTno(2);        System.out.println("student1:"+student1);        System.out.println("student2:"+student2);        System.out.println("student1 == student2 ? ==> "+ (student1 ==student2));        System.out.println("student1.teacher == student2.teacher ? ==> "+ (student1.getTeacher() ==student2.getTeacher()));    }

 

如果Teacher類或者Student類沒有實現序列化介面,則執行時會報異常,如下:

java.io.NotSerializableException: com.example.test.Teacher

 

在都實現了Serializable介面的情況下,運行結果如下:

student1:Student{sno=1, name=‘Rye‘, teacher=Teacher{tno=2, name=‘LinTong‘}}student2:Student{sno=1, name=‘Rye‘, teacher=Teacher{tno=1, name=‘LinTong‘}}student1 == student2 ? ==> falsestudent1.teacher == student2.teacher ? ==> false
View Code

 

由此通過物件流程的方式,成功完成了深度拷貝;

 

 

三.重寫clone方法 與 通過序列化 兩種拷貝方式比較:

clone方法:

優點:速度快,效率高

缺點:在對象引用比較深時,使用此方式比較繁瑣

 

通過序列化:

優點:非常簡便的就可以完成深度copy

缺點:由於序列化的過程需要跟磁碟打交道,因此效率會低於clone方式

 

如何抉擇?

實際開發中,根據兩種方式的優缺點進行選擇即可!

 

淺談關於java中的深淺拷貝

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.