【Java學習】Clone 分析

如果餓了就吃,困了就睡,渴了就喝,人生就太無趣了
作者:可耳(keer)
更新時間 : 2020年04月01日
源碼地址:https://github.com/keer123456789/java_study_demo


1.概念

1.1 淺拷貝(淺複製、淺克隆):

被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺拷貝僅僅複製所拷貝的對象,而不復制它所引用的對象。
如圖1:對象obj1經過淺克隆生成對象obj2,但是二者都指向相同地址,這樣只要修改任意一個對象值,另一對象也會改變

在這裏插入圖片描述

1.2 深拷貝(深複製、深克隆):

被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深拷貝把要複製的對象所引用的對象都複製了一遍
如圖2:對象obj1經過深克隆生成對象obj2,此時obj2obj1指向不同的內存地址,修改任何一方,都不會引起另一方的改變。

在這裏插入圖片描述

2.不帶屬性對象

2.1 淺拷貝

public class User {
    private String num;

    public String getNum() {
        return num;
    }

    public void setNum(String num) {
        this.num = num;
    }

    public static void main(String[] args) {
        User user1=new User();
        user1.setNum("123456");
        User user2= user1;
        User user3=new User();
		user3.setNum("123456");
        System.out.println("克隆後,未做修改前,兩用戶的信息:");
        System.out.println("User1.num:"+user1.getNum());
        System.out.println("User2.num:"+user2.getNum());
        System.out.println("User1和User2是否相等:"+user1.equals(user2));
        user2.setNum("7890");
        System.out.println("克隆後,修改User2.num=:"+user2.getNum()+",兩用戶信息如下:");
        System.out.println("User1.num:"+user1.getNum());
        System.out.println("User2.num:"+user2.getNum());
        System.out.println("User1和User2是否相等:"+user1.equals(user2));
    }
}

運行結果:
在這裏插入圖片描述

觀察debug運行時的內存地址(@後面是該變量的相對內存地址):
可以看到user2是經過user1淺克隆的,二者都指向同一個內存地址,user3是單獨的User對象的實例,雖然具有相同的值,但是user3有單獨的內存地址。
在這裏插入圖片描述

2.2 深拷貝

public class User implements Cloneable {
    private String num;

    public String getNum() {
        return num;
    }

    public void setNum(String num) {
        this.num = num;
    }
    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        User user1=new User();
        user1.setNum("123456");
        User user2= user1.clone();
        System.out.println("克隆後,未做修改前,兩用戶的信息:");
        System.out.println("User1.num:"+user1.getNum());
        System.out.println("User2.num:"+user2.getNum());
        System.out.println("User1和User2是否相等:"+user1.equals(user2));
        user2.setNum("7890");
        System.out.println("克隆後,修改User2.num=:"+user2.getNum()+",兩用戶信息如下:");
        System.out.println("User1.num:"+user1.getNum());
        System.out.println("User2.num:"+user2.getNum());
        System.out.println("User1和User2是否相等:"+user1.equals(user2));
    }
}

運行結果:
可以看到對user2進行修改,沒有對user1產生影響。
在這裏插入圖片描述

觀察debug運行時的內存地址:此時內存地址都是不同的。
在這裏插入圖片描述

3.帶有屬性對象

3.1 淺拷貝(屬性對象沒有實現Cloneable接口)

public class Address {
    private String add;

    public String getAdd() {
        return add;
    }

    public void setAdd(String add) {
        this.add = add;
    }
}

public class Student implements Cloneable {
    private int number;

    private Address addr;

    public Address getAddr() {
        return addr;
    }

    public void setAddr(Address addr) {
        this.addr = addr;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    @Override
    public Object clone() {
        Student stu = null;
        try {
            stu = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return stu;
    }

    public static void main(String[] args) {
        Address addr = new Address();
        addr.setAdd("杭州市");
        Student stu1 = new Student();
        stu1.setNumber(123);
        stu1.setAddr(addr);
        Student stu2 = (Student) stu1.clone();
        System.out.println("克隆後,未做修改前,學生的信息:");
        System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        System.out.println("stu1 和stu2是否相等:"+stu1.equals(stu2));
        System.out.println("stu1 和stu2的地址是否相等:"+stu1.getAddr().equals(stu2.getAddr()));
        stu1.setNumber(3333);
        stu1.getAddr().setAdd("中國");
        System.out.println("克隆後,修改stu1後,學生的信息:");
        System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        System.out.println("stu1 和stu2是否相等:"+stu1.equals(stu2));
        System.out.println("stu1 和stu2的地址是否相等:"+stu1.getAddr().equals(stu2.getAddr()));

    }
}

運行結果:

在這裏插入圖片描述
修改其中一個學生的信息,兩個學生的地址信息會隨着改變而變化,但是學號不會改變。看一下運行時的內存情況:

在修改學生信息前:

在這裏插入圖片描述

  • 兩個學生的實例的內存地址是不同的
  • 由於地址對象沒有實現Cloneable接口,所以每個學生對象的實例的地址屬性的內存地址都指向同一個地址。

在修改學生信息後:

在這裏插入圖片描述

  • 地址的變化會隨着改變而導致所有的學生實例中的地址屬性也發生變化。
  • 一個實例的學號改變,不會影響其他人的信息。

3.2 淺拷貝(屬性對象實現Cloneable接口,但未顯示的調用)

public class Address2 implements Cloneable{
    private String add;

    public String getAdd() {
        return add;
    }

    public void setAdd(String add) {
        this.add = add;
    }

    @Override
    public Object clone() {
        Address2 address = null;
        try {
            address = (Address2) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return address;
    }

}


public class Student2 implements Cloneable{
    private int number;

    private Address2 addr;

    public Address2 getAddr() {
        return addr;
    }

    public void setAddr(Address2 addr) {
        this.addr = addr;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    @Override
    public Object clone() {
        Student2 stu = null;
        try {
            stu = (Student2) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return stu;
    }

    public static void main(String[] args) {
        Address2 addr = new Address2();
        addr.setAdd("杭州市");
        Student2 stu1 = new Student2();
        stu1.setNumber(123);
        stu1.setAddr(addr);
        Student2 stu2 = (Student2) stu1.clone();
        System.out.println("克隆後,未做修改前,學生的信息:");
        System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        System.out.println("stu1 和stu2是否相等:"+stu1.equals(stu2));
        System.out.println("stu1 和stu2的地址是否相等:"+stu1.getAddr().equals(stu2.getAddr()));
        stu1.setNumber(3333);
        stu1.getAddr().setAdd("中國");
        System.out.println("克隆後,修改stu1後,學生的信息:");
        System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        System.out.println("stu1 和stu2是否相等:"+stu1.equals(stu2));
        System.out.println("stu1 和stu2的地址是否相等:"+stu1.getAddr().equals(stu2.getAddr()));

    }
}

運行結果:

在這裏插入圖片描述

和之前的相似,地址屬性依舊沒有進行克隆,只是將學生進行了淺拷貝。

運行時內存的分佈:

在這裏插入圖片描述

  • 兩個學生實例的地址屬性都指向同一個地址。

3.3 深拷貝(屬性對象實現Cloneable接口,顯示調用)

public class Address implements Cloneable{
    private String add;

    public String getAdd() {
        return add;
    }

    public void setAdd(String add) {
        this.add = add;
    }

    @Override
    public Object clone() {
        Address address = null;
        try {
            address = (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return address;
    }
}

public class Student implements Cloneable {
    private int number;

    private Address addr;

    public Address getAddr() {
        return addr;
    }

    public void setAddr(Address addr) {
        this.addr = addr;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    @Override
    public Object clone() {
        Student stu = null;
        try {
            stu = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        // 對象屬性設置
        stu.addr = (Address) addr.clone();

        return stu;
    }

    public static void main(String[] args) {
        Address addr = new Address();
        addr.setAdd("北京市");
        Student stu1 = new Student();
        stu1.setNumber(123);
        stu1.setAddr(addr);
        Student stu2 = (Student) stu1.clone();
        System.out.println("克隆後,未做修改前,學生的信息:");
        System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        System.out.println("stu1 和stu2是否相等:" + stu1.equals(stu2));
        System.out.println("stu1 和stu2的地址是否相等:" + stu1.getAddr().equals(stu2.getAddr()));
        stu1.setNumber(3333);
        stu1.getAddr().setAdd("中國");
        System.out.println("克隆後,修改stu1後,學生的信息:");
        System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        System.out.println("stu1 和stu2是否相等:" + stu1.equals(stu2));
        System.out.println("stu1 和stu2的地址是否相等:" + stu1.getAddr().equals(stu2.getAddr()));
    }
}

運行結果:

在這裏插入圖片描述
此次克隆過後,兩個學生實例的地址屬性不是相等的,修改其中一個學生的地址屬性,不會引起其實例的地址屬性變化。

運行時內存的分佈:

在這裏插入圖片描述

兩個學生的地址屬性的所在的內存地址是不同的。

參考文章:

https://blog.csdn.net/dengjili/article/details/85716058

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