首頁 > 軟體

Java中深拷貝,淺拷貝與參照拷貝的區別詳解

2022-08-23 14:02:27

參照拷貝

參照拷貝: 參照拷貝不會在堆上建立一個新的物件,只 會在棧上生成一個新的參照地址,最終指向依然是堆上的同一個物件

//實體類
public class Person{
    public String name;//姓名
    public int height;//身高
    public StringBuilder something;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public StringBuilder getSomething() {
        return something;
    }

    public void setSomething(StringBuilder something) {
        this.something = something;
    }

    public Person(String name, int height, StringBuilder something) {
        this.name = name;
        this.height = height;
        this.something = something;
    }

}

//測試類
public class copyTest {
    public static void main(String[] args) {
        Person p1 = new Person("小張", 180, new StringBuilder("今天天氣很好"));
        Person p2 = p1;

        System.out.println("物件是否相等:"+ (p1 == p2));
        System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
        System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());


        // change
        p1.name="小王";
        p1.height = 200;
        p1.something.append(",適合出去玩");
        System.out.println("...after p1 change....");

        System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
        System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());

    }
}

結果:

物件是否相等:true
p1 屬性值=小張,180,今天天氣很好
p2 屬性值=小張,180,今天天氣很好
...after p1 change....
p1 屬性值=小王,200,今天天氣很好,適合出去玩
p2 屬性值=小王,200,今天天氣很好,適合出去玩

before change:

after change:

我們可以看出 由於2個參照p1,p2 都是指向堆中同一個物件,所以2個物件是相等的,修改了物件p1,會影響到物件p2
需要注意的

  • name屬性,雖然她是參照型別,但她同時也是String型別,不可變,對其修改,JVM會預設在堆上建立新的記憶體空間,再重新賦值
  • int weight=180; 是成員變數,存放在堆中,不是所有的基本型別變數 都存放在JVM棧中

注意與這篇文章得區分開來, int num1 = 10;是基本型別的區域性變數,存放在棧中

淺拷貝

淺拷貝 :淺拷貝會在堆上建立一個新的物件,新物件和原物件不等,但是新物件的屬性和老物件相同

其中:

  • 如果屬性是基本型別(int,double,long,boolean等),拷貝的就是基本型別的值。
  • 如果屬性是參照型別(除了基本型別都是參照型別),拷貝的就是引⽤資料型別變數的地址值,⽽對於引⽤型別變數指向的堆中的物件不會拷貝。

如何實現淺拷貝呢?也很簡單,就是在需要拷貝的類上實現Cloneable介面並重寫其clone()方法

@Override protected Object clone() throws CloneNotSupportedException {   
    return super.clone(); 
} 

在使用的時候直接呼叫類的clone()方法即可

//實體類 繼承Cloneable
public class Person implements Cloneable{
    public String name;//姓名
    public int height;//身高
    public StringBuilder something;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public StringBuilder getSomething() {
        return something;
    }

    public void setSomething(StringBuilder something) {
        this.something = something;
    }

    public Person(String name, int height, StringBuilder something) {
        this.name = name;
        this.height = height;
        this.something = something;
    }



    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }

}

//測試類
public class shallowCopyTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person("小張", 180, new StringBuilder("今天天氣很好"));
        Person p2 = p1.clone();

        System.out.println("物件是否相等:"+ (p1 == p2));
        System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
        System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());


        // change
        p1.setName("小王");
        p1.setHeight(200);
        p1.getSomething().append(",適合出去玩");
        System.out.println("...after p1 change....");

        System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
        System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());

    }
}

結果:

物件是否相等:false
p1 屬性值=小張,180,今天天氣很好
p2 屬性值=小張,180,今天天氣很好
...after p1 change....
p1 屬性值=小王,200,今天天氣很好,適合出去玩
p2 屬性值=小張,180,今天天氣很好,適合出去玩

before change:

after change:

我們可以看出:

  • 當我們修改物件p1的weight屬性時,由於p2的height屬性 是直接複製修改前的p1的height屬性,所以還是180。
  • 當我們修改物件p1的name屬性 時,String name指向一個新的記憶體空間,但物件p2的name還是指向舊的記憶體空間,所以物件p2的name屬性還是"小張"。
  • 由於物件p1的something屬性和物件p2的something屬性指向是同一個記憶體空間,當我們修改物件p1的something屬性,會影響到物件p2的something屬性,所以物件p2的something屬性變為"今天天氣很好,適合出去玩"。

深拷貝

深拷貝 :完全拷貝⼀個物件,在堆上建立一個新的物件,拷貝被拷貝物件的成員變數的值,同時堆中的物件也會拷貝。
需要重寫clone方法

    @Override
    public Person clone() throws CloneNotSupportedException {
        //return (Person) super.clone();
        Person person = (Person) super.clone();
        person.setSomething( new StringBuilder(person.getSomething()));//單獨為參照型別clone
        return person;
    }

shallowCopyTest測試類的結果:

物件是否相等:false
p1 屬性值=小張,180,今天天氣很好
p2 屬性值=小張,180,今天天氣很好
...after p1 change....
p1 屬性值=小王,200,今天天氣很好,適合出去玩
p2 屬性值=小張,180,今天天氣很好

這時候物件p1和物件p2互不干擾了

before change:

after change:

但這樣也有個小問題,物件每有一個參照型別,我們都得重寫其clone方法,這樣會非常麻煩,因此我們還可以藉助序列化來實現物件的深拷貝

//實體類 繼承Cloneable
public class Person implements Serializable{
    public String name;//姓名
    public int height;//身高
    public StringBuilder something;

...//省略 getter setter


    public Object deepClone() throws Exception{
        // 序列化
        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();
    }

}

//測試類,這邊類名筆者就不換了,在之前的基礎上改改
public class shallowCopyTest {

    public static void main(String[] args) throws Exception {
        Person p1 = new Person("小張", 180, new StringBuilder("今天天氣很好"));
        Person p2 = (Person)p1.deepClone();

        System.out.println("物件是否相等:"+ (p1 == p2));
        System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
        System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());


        // change
        p1.setName("小王");
        p1.setHeight(200);
        p1.getSomething().append(",適合出去玩");
        System.out.println("...after p1 change....");

        System.out.println("p1 屬性值=" + p1.getName()+ ","+ p1.getHeight() + ","+ p1.getSomething());
        System.out.println("p2 屬性值=" + p2.getName()+ ","+ p2.getHeight() + ","+ p2.getSomething());

    }
}

這樣也會得到深拷貝的結果

小結

參照拷貝: 參照拷貝不會在堆上建立一個新的物件,只 會在棧上生成一個新的參照地址,最終指向依然是堆上的同一個物件

淺拷貝 :淺拷貝會在堆上建立一個新的物件,新物件和原物件不等,但是新物件的屬性和老物件相同

其中:

如果屬性是基本型別(int,double,long,boolean等),拷貝的就是基本型別的值。

如果屬性是參照型別(除了基本型別都是參照型別),拷貝的就是引⽤資料型別變數的地址值,⽽對於引⽤型別變數指向的堆中的物件不會拷貝。

深拷貝 :完全拷貝⼀個物件,在堆上建立一個新的物件,拷貝被拷貝物件的成員變數的值,同時堆中的物件也會拷貝。

以上就是Java中深拷貝,淺拷貝與參照拷貝的區別詳解的詳細內容,更多關於Java拷貝的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com