Java 深克隆與淺克隆 詳解

#一、克隆的作用
快速構建一個和已有對象相同的副本,創建一個新對象,將已有對象的數據導入到新對象裏面;

#二、克隆基本簡介
我們說的克隆,都是基於超類 Object 來的,裏面有個native方法,具體實現是它調用底層C語言的實現,我們是看不到的

protected native Object clone() throws CloneNotSupportedException;

由此可知,有幾個約束

  1. 使用時必須繼承Object類,我們所有的類都是Object派生的
  2. 接收對象必須強轉
  3. 必須實現 Cloneable 接口標識 (表示重寫了clone() )

不實現這個接口會發生什麼呢?

public class User{

    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

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

    public static void main(String[] args) throws CloneNotSupportedException {
        User user=new User();
        user.setUserName("zhangsan");
        System.out.println(JSONObject.toJSONString(user));
        User user1= (User) user.clone();
        System.out.println(JSONObject.toJSONString(user1));
    }
}

結果:

{"userName":"zhangsan"}
Exception in thread "main" java.lang.CloneNotSupportedException: com.demo.cs.study.clones.User
	at java.lang.Object.clone(Native Method)
	at com.demo.cs.study.clones.User.clone(User.java:19)
	at com.demo.cs.study.clones.User.main(User.java:32)

添加實現後

public class User implements Cloneable{

    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

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

    public static void main(String[] args) throws CloneNotSupportedException {
        User user=new User();
        user.setUserName("zhangsan");
        System.out.println(JSONObject.toJSONString(user));
        User user1= (User) user.clone();
        System.out.println(JSONObject.toJSONString(user1));
    }
}

結果:

{"userName":"zhangsan"}
{"userName":"zhangsan"}

1、淺克隆

概念:如果被複制的對象所有的變量與原來的變量值相同,且所持有對其它對象的引用任然指向原來的對象就叫淺克隆

User.java

public class User implements Cloneable{

    private String userName;

    private HavingDinner havingDinner;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public HavingDinner getHavingDinner() {
        return havingDinner;
    }

    public void setHavingDinner(HavingDinner havingDinner) {
        this.havingDinner = havingDinner;
    }

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

HavingDinner.java

public class HavingDinner {

    private String foodName;

    public String getFoodName() {
        return foodName;
    }

    public void setFoodName(String foodName) {
        this.foodName = foodName;
    }

    public HavingDinner(String foodName) {
        this.foodName = foodName;
    }
}

Demo.java

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        HavingDinner hd=new HavingDinner("青椒炒蘿蔔");
        User user=new User();
        user.setUserName("哈比");
        user.setHavingDinner(hd);
        User user1= (User) user.clone();
        user1.setUserName("哈比2");
        User user2= (User) user.clone();
        user2.setUserName("哈士奇");
        System.out.println(user.getUserName()+"-->今天晚上要喫"+user.getHavingDinner().getFoodName()+"  引用地址:"+user.getHavingDinner());
        System.out.println(user1.getUserName()+"-->今天晚上要喫"+user1.getHavingDinner().getFoodName()+"  引用地址:"+user1.getHavingDinner());
        System.out.println(user2.getUserName()+"-->今天晚上要喫"+user2.getHavingDinner().getFoodName()+"  引用地址:"+user2.getHavingDinner());
    }
}

結果:

哈比-->今天晚上要喫青椒炒蘿蔔  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380
哈比2-->今天晚上要喫青椒炒蘿蔔  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380
哈士奇-->今天晚上要喫青椒炒蘿蔔  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380

看輸出結果,引用地址一樣。這就是淺克隆;那麼問題來了,哈比要喫土豆雞塊,我們改下

Demo2

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        HavingDinner hd=new HavingDinner("土豆雞塊");
        User user=new User();
        user.setUserName("哈比");
        user.setHavingDinner(hd);
        User user1= (User) user.clone();
        user1.setUserName("哈比2");
        User user2= (User) user.clone();
        user2.setUserName("哈士奇");
        System.out.println(user.getUserName()+"-->今天晚上要喫"+user.getHavingDinner().getFoodName()+"  引用地址:"+user.getHavingDinner());
        System.out.println(user1.getUserName()+"-->今天晚上要喫"+user1.getHavingDinner().getFoodName()+"  引用地址:"+user1.getHavingDinner());
        System.out.println(user2.getUserName()+"-->今天晚上要喫"+user2.getHavingDinner().getFoodName()+"  引用地址:"+user2.getHavingDinner());
    }
}

結果:

哈比-->今天晚上要喫土豆雞塊  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380
哈比2-->今天晚上要喫土豆雞塊  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380
哈士奇-->今天晚上要喫土豆雞塊  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380

會發現其它人都給喫土豆雞塊了,我們再改下哈比2的試試

Demo3:

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        HavingDinner hd=new HavingDinner("土豆雞塊");
        User user=new User();
        user.setUserName("哈比");
        user.setHavingDinner(hd);
        User user1= (User) user.clone();
        user1.setUserName("哈比2");
        user1.getHavingDinner().setFoodName("冰淇淋");
        User user2= (User) user.clone();
        user2.setUserName("哈士奇");
        System.out.println(user.getUserName()+"-->今天晚上要喫"+user.getHavingDinner().getFoodName()+"  引用地址:"+user.getHavingDinner());
        System.out.println(user1.getUserName()+"-->今天晚上要喫"+user1.getHavingDinner().getFoodName()+"  引用地址:"+user1.getHavingDinner());
        System.out.println(user2.getUserName()+"-->今天晚上要喫"+user2.getHavingDinner().getFoodName()+"  引用地址:"+user2.getHavingDinner());
    }
}

結果:

哈比-->今天晚上要喫冰淇淋  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380
哈比2-->今天晚上要喫冰淇淋  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380
哈士奇-->今天晚上要喫冰淇淋  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380

結果還是 都喫冰淇淋,這樣的話就有問題了,我想哈比喫土豆雞塊,哈比2喫冰淇淋;
這樣也證實了上面的概念;基本屬性的值是複製了,但是其它對象裏的屬性根本就沒有複製,只是單純的複製了引用地址;

2、深克隆

User.java

public class User implements Cloneable{

    private String userName;

    private HavingDinner havingDinner;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public HavingDinner getHavingDinner() {
        return havingDinner;
    }

    public void setHavingDinner(HavingDinner havingDinner) {
        this.havingDinner = havingDinner;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException{
        User user=(User) super.clone();
        user.setHavingDinner((HavingDinner) getHavingDinner().clone());
        return user;
    }
}

HavingDinner.java

public class HavingDinner implements Cloneable{

    private String foodName;

    public String getFoodName() {
        return foodName;
    }

    public void setFoodName(String foodName) {
        this.foodName = foodName;
    }

    public HavingDinner(String foodName) {
        this.foodName = foodName;
    }

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

Demo.java

public class Demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        HavingDinner hd=new HavingDinner("土豆雞塊");
        User user=new User();
        user.setUserName("哈比");
        user.setHavingDinner(hd);
        User user1= (User) user.clone();
        user1.setUserName("哈比2");
        user1.getHavingDinner().setFoodName("冰淇淋");
        User user2= (User) user.clone();
        user2.setUserName("哈士奇");
        System.out.println(user.getUserName()+"-->今天晚上要喫"+user.getHavingDinner().getFoodName()+"  引用地址:"+user.getHavingDinner());
        System.out.println(user1.getUserName()+"-->今天晚上要喫"+user1.getHavingDinner().getFoodName()+"  引用地址:"+user1.getHavingDinner());
        System.out.println(user2.getUserName()+"-->今天晚上要喫"+user2.getHavingDinner().getFoodName()+"  引用地址:"+user2.getHavingDinner());
    }
}

結果:

哈比-->今天晚上要喫土豆雞塊  引用地址:com.demo.cs.study.clones.HavingDinner@12a3a380
哈比2-->今天晚上要喫冰淇淋  引用地址:com.demo.cs.study.clones.HavingDinner@29453f44
哈士奇-->今天晚上要喫土豆雞塊  引用地址:com.demo.cs.study.clones.HavingDinner@5cad8086

總結:達到了互不影響的結果,對象裏面包含的對象也複製了,這是第一種方式,有點複雜;
2、序列化深克隆
原理:把對象序列化輸出到流裏面,然後把流裏面的數據序列化出來,得到一個新的對象
事例:
User.java

public class User implements Serializable {

    private String userName;

    private HavingDinner havingDinner;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public HavingDinner getHavingDinner() {
        return havingDinner;
    }

    public void setHavingDinner(HavingDinner havingDinner) {
        this.havingDinner = havingDinner;
    }

    protected User deepClone() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);
        ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
        return (User) objectInputStream.readObject();
    }
}

HavingDinner.java

public class HavingDinner implements Serializable {

    private String foodName;

    public String getFoodName() {
        return foodName;
    }

    public void setFoodName(String foodName) {
        this.foodName = foodName;
    }

    public HavingDinner(String foodName) {
        this.foodName = foodName;
    }
}

測試類

public class Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HavingDinner hd=new HavingDinner("土豆雞塊");
        User user=new User();
        user.setUserName("哈比");
        user.setHavingDinner(hd);
        User user1=user.deepClone();
        user1.setUserName("哈比2");
        user1.getHavingDinner().setFoodName("冰淇淋");
        User user2=user.deepClone();
        user2.setUserName("哈士奇");
        System.out.println(user.getUserName()+"-->今天晚上要喫"+user.getHavingDinner().getFoodName()+"  引用地址:"+user.getHavingDinner());
        System.out.println(user1.getUserName()+"-->今天晚上要喫"+user1.getHavingDinner().getFoodName()+"  引用地址:"+user1.getHavingDinner());
        System.out.println(user2.getUserName()+"-->今天晚上要喫"+user2.getHavingDinner().getFoodName()+"  引用地址:"+user2.getHavingDinner());
    }
}

結果:

哈比-->今天晚上要喫土豆雞塊  引用地址:com.demo.cs.study.clones.HavingDinner@5e2de80c
哈比2-->今天晚上要喫冰淇淋  引用地址:com.demo.cs.study.clones.HavingDinner@34a245ab
哈士奇-->今天晚上要喫土豆雞塊  引用地址:com.demo.cs.study.clones.HavingDinner@7cc355be
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章