淺談關於java中的深淺拷貝


一.淺拷貝(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中的sno
s1.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 ? ==> false
student1.teacher == student2.teacher ? ==> true
View Code

 

由此可見,此時經過clone生成的student2, 與 student1.二者中的teacher字段, 指向同一塊內存空間;

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

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

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

//修改teacher中的 tno值為2
student1.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 ? ==> false
student1.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 ? ==> false
student1.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 ? ==> false
student1.teacher == student2.teacher ? ==> false
View Code

 

由此通過對象流的方式,成功完成了深度拷貝;

 


 

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

clone方法:

優點:速度快,效率高

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

 

通過序列化:

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

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

 

如何抉擇?

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM