Java 對象克隆(clone)

基本數據類型(boolean,char,byte,short,float,double,long)的複製很簡單,比如:

int width = 5int height = width;

基本數據類型進行這樣複製是沒有問題的。

按照上面的方法進行對象的複製,首先自定義一個汽車類,該類有一個color 屬性,然後新建一個汽車實例car,然後將car 賦值給car1 即car1= car;
代碼和結果如下:

public static void main(String[] args) {

    Car car = new Car();
    car.setColor("white");
    Car car1 = car;

    System.out.println("car color: " + car.getColor());
    System.out.println("car1 color: " + car1.getColor());
}


private static class Car {

    private Car() {
    }

   private String color;

    public void setColor(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }
}

輸出結果:

car color: white
car1 color: white

從打印來看沒有什麼問題, 那麼我們對代碼更改如下:

public static void main(String[] args) {

    Car car = new Car();
    car.setColor("white");
    Car car1 = car;
    car1.setColor("blue");

    System.out.println("car color: " + car.getColor());
    System.out.println("car1 color: " + car1.getColor());
}

輸出結果:

car color: blue
car1 color: blue

爲什麼會出現這種結果呢? 給car1 賦值 結果car 的值也變了。

問題的原因是,car1= car 這行語句,該語句的作用是將car的引用賦值給car1,其實car 與car1 指向內存堆中的同一個對象,如下圖:

這裏寫圖片描述

對car 和car1 操作實際上市操作的同一個對象。
如何複製對象呢 ? 我們知道所有的類都集成Object, 方法如下:

protected Object clone() throws CloneNotSupportedException {
    if (!(this instanceof Cloneable)) {
        throw new CloneNotSupportedException("Class " + getClass().getName() +
                                             " doesn't implement Cloneable");
    }

    return internalClone();
}

/*
 * Native helper method for cloning.
 */
private native Object internalClone();

從代碼中可以看出要實現clone 方法的類必須實現Clonenable 接口,最終是通過native方法,大家都知道native方法是非Java語言實現的代碼,供Java程序調用的,因爲Java程序是運行在JVM虛擬機上面的,要想訪問到比較底層的與操作系統相關的就沒辦法了,只能由靠近操作系統的語言來實現。
第一次聲明保證克隆對象將有單獨的內存地址分配。
第二次聲明表明,原始和克隆的對象應該具有相同的類類型,但它不是強制性的。
第三聲明表明,原始和克隆的對象應該是平等的equals()方法使用,但它不是強制性的。
因爲每個類直接或間接的父類都是Object,因此它們都含有clone()方法,但是因爲該方法是protected,所以都不能在類外進行訪問。要想對一個對象進行復制,就需要對clone方法覆蓋。

爲什麼要Clone

有時候需要clone 一個對象修改過的屬性,然而通過new創建的對象的屬性是初始值,所以當需要一個新的對象來保存當前對象的“狀態”就靠clone方法了。如果把對象的臨時屬性一個一個的賦值給我新new的對象,操作起來比較麻煩,並且速度沒有底層實現的快。

如何實現clone

clone 分爲兩種, 淺克隆(ShallowClone)和深克隆(DeepClone)。在Java語言中,數據類型分爲值類型(基本數據類型)和引用類型,值類型包括int、double、byte、boolean、char等簡單數據類型,引用類型包括類、接口、數組等複雜類型。淺克隆和深克隆的主要區別在於是否支持引用類型的成員變量的複製,下面將對兩者進行詳細介紹。

淺克隆

1、被複制的類需要實現Clonenable接口(不實現的話在調用clone方法會拋出CloneNotSupportedException異常), 該接口爲標記接口(不含任何方法)
2、 覆蓋clone()方法,訪問修飾符設爲public。方法中調用super.clone()方法得到需要的複製對象。(native爲本地方法)

下面對Car 類進行改造:

public class TestJava {

    public static void main(String[] args) {

        Car car = new Car();
        car.setColor("white");
        Car car1 = (Car) car.clone();
        car1.setColor("blue");

        if (car1 != null) {
            System.out.println("car color: " + car.getColor());
        }
        System.out.println("car1 color: " + car1.getColor());

        System.out.println("car == car1?" + (car == car1));
    }


    private static class Car implements Cloneable {

        private Car() {
        }

       private String color;

        public void setColor(String color) {
            this.color = color;
        }

        public String getColor() {
            return color;
        }

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

通過查看打印信息可以看出car 和 car1 不是指向的同一個對象,上面實現的這種clone 爲淺clone, 還有一種clone 爲深clone

深clone

我們對Car 類進行改造,增加一個Engine屬性

public class TestJava {

    public static void main(String[] args) {
        Engine engine = new Engine();
        engine.setModel("CA");
        Car car = new Car();
        car.setEngine(engine);
        Car newCar = (Car) car.clone();
        newCar.getEngine().setModel("CY");
        System.out.println("Car Engine Model:" + car.getEngine().getModel());
        System.out.println("newCar Engine Model: " + newCar.getEngine().getModel());
    }


    private static class Car implements Cloneable {

        private Car() {

        }

        private Engine engine;
        private String color;

        public void setColor(String color) {
            this.color = color;
        }

        public String getColor() {
            return color;
        }

        public void setEngine(Engine engine) {
            this.engine = engine;
        }

        public Engine getEngine() {
            return engine;
        }

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

    private static class Engine {

        private String model;

        public void setModel(String model) {
            this.model = model;
        }

        public String getModel() {
            return model;
        }
    }
}


Car Engine Model:CY

newCar Engine Model: CY

通過打印可以看改變了newCar對象的Engine 屬性 car 的Engine 屬性也變了,

原因是淺複製只是複製了engine變量的引用,並沒有真正的開闢另一塊空間,將值複製後再將引用返回給新對象。

所以,爲了達到真正的複製對象,而不是純粹引用複製。我們需要將Engine類可複製化,並且修改clone方法,完整代碼如下:

public class TestJava {

    public static void main(String[] args) {
        Engine engine = new Engine();
        engine.setModel("CA");
        Car car = new Car();
        car.setEngine(engine);
        Car newCar = (Car) car.clone();
        newCar.getEngine().setModel("CY");
        System.out.println("Car Engine Model:" + car.getEngine().getModel());
        System.out.println("newCar Engine Model: " + newCar.getEngine().getModel());
    }


    private static class Car implements Cloneable {

        private Car() {

        }

        private Engine engine;
        private String color;

        public void setColor(String color) {
            this.color = color;
        }

        public String getColor() {
            return color;
        }

        public void setEngine(Engine engine) {
            this.engine = engine;
        }

        public Engine getEngine() {
            return engine;
        }

        @Override
        protected Object clone() {
            Car car = null;
            try {
                car = (Car) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
                return null;
            }
            car.engine = (Engine) engine.clone();

            return car;
        }
    }

    private static class Engine implements Cloneable{

        private String model;

        public void setModel(String model) {
            this.model = model;
        }

        public String getModel() {
            return model;
        }


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

輸出結果:

Car Engine Model:CA
newCar Engine Model: CY

輸出結果與我們的想法一致

最後Java util 中clone 的實現“

/**
 * Return a copy of this object.
 */
public Object clone() {
    Date d = null;
    try {
        d = (Date)super.clone();
        if (cdate != null) {
            d.cdate = (BaseCalendar.Date) cdate.clone();
        }
    } catch (CloneNotSupportedException e) {} // Won't happen
    return d;
}

總結 淺克隆 與深克隆

1、淺克隆

在淺克隆中,如果原型對象的成員變量是基本類型,將複製一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址複製一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。

這裏寫圖片描述

簡單來說,在淺克隆中,當對象被複制時只複製它本身和其中包含的值類型的成員變量,而引用類型的成員對象並沒有複製。

2、深克隆

在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將複製一份給克隆對象,深克隆將原型對象的所有引用對象也複製一份給克隆對象。

這裏寫圖片描述

簡單來說,在深克隆中,除了對象本身被複制外,對象所包含的所有成員變量也將複製。

序列化實現深克隆

序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在於內存中。通過序列化實現的拷貝不僅可以複製對象本身,而且可以複製其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流裏將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的對象其類必須實現Serializable接口,否則無法實現序列化操作。

Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單,它們都是空接口,這種空接口也稱爲標識接口,標識接口中沒有任何方法的定義,其作用是告訴JRE這些接口的實現類是否具有某個功能,如是否支持克隆、是否支持序列化等。

實現方法:

public class TestJava {

    public static void main(String[] args) {
        Child child = new Child();
        child.name = "Tony";

        Parent parent = new Parent();
        parent.setChild(child);

        Parent newParent = parent.clone();
        newParent.getChild().name = "Steven";

        System.out.println("parent child name:" + parent.getChild().name);
        System.out.println("new parent child name:" + newParent.getChild().name);
    }


    public static class Parent implements Serializable {

        private static final long serialVersionUID = 369285298572941L;
        private Child child;

        public void setChild(Child child) {
            this.child = child;
        }

        public Child getChild() {
            return child;
        }

        public Parent clone() {
            Parent parent = null;

            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(this);

                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bais);
                parent = (Parent) ois.readObject();

            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return parent;

        }
    }

    public static class Child implements Serializable {

        private static final long serialVersionUID = 872390113109L;

        public String name;

        @Override
        public String toString() {
            return "name :" + name;
        }
    }


}

實現對象克隆有兩種方式:

  1). 實現Cloneable接口並重寫Object類中的clone()方法;

  2). 實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆。

基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來總是優於把問題留到運行時。

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