Java對象的拷貝

對象的拷貝

深度拷貝一個對象

java.lang.Object 根類已經定義了 clone() 方法。子類只需要事項 java.lang.Cloneable 接口,就可以返回一個拷貝的對象。實現接口的同時要實現 clone() 方法里面的實現。這是拷貝方式就是淺拷貝(shallow copy of object),這代表著原有對象的值復制一份存儲在一個新的對象里面。

淺拷貝并不是完全復制原對象的所有內容,除了基本類型(int,float...),引用類型只是復制一份引用對象的引用,那么原有的對象和拷貝的對象就指共同指向同一個引用對象。這樣使用淺拷貝就會導致一些不可預測的錯誤。看看項目 Exampl1的例子。

public class Example1 {

    public static void main(String[] args) {
        // Make a Vector
        Vector original = new Vector();

        // Make a StringBuffer and add it to the Vector
        StringBuffer text = new StringBuffer("The quick brown fox");
        original.addElement(text);

        // Clone the vector and print out the contents
        Vector clone = (Vector) original.clone();
        System.out.println("A. After cloning");
        printVectorContents(original, "original");
        printVectorContents(clone, "clone");
        System.out.println(
            "--------------------------------------------------------");
        System.out.println();

        // Add another object (an Integer) to the clone and 
        // print out the contents
        clone.addElement(new Integer(5));
        System.out.println("B. After adding an Integer to the clone");
        printVectorContents(original, "original");
        printVectorContents(clone, "clone");
        System.out.println(
            "--------------------------------------------------------");
        System.out.println();

        // Change the StringBuffer contents
        text.append(" jumps over the lazy dog.");
        System.out.println("C. After modifying one of original's elements");
        printVectorContents(original, "original");
        printVectorContents(clone, "clone");
        System.out.println(
            "--------------------------------------------------------");
        System.out.println();
    }

    public static void printVectorContents(Vector v, String name) {
        System.out.println("  Contents of \"" + name + "\":");

        // For each element in the vector, print out the index, the
        // class of the element, and the element itself
        for (int i = 0; i < v.size(); i++) {
            Object element = v.elementAt(i);
            System.out.println("   " + i + " (" + 
                element.getClass().getName() + "): " + 
                element);
        }
        System.out.println();
    }

}

Exampl1 運行輸出的結果:

A. After cloning
  Contents of "original":
   0 (java.lang.StringBuffer): The quick brown fox

  Contents of "clone":
   0 (java.lang.StringBuffer): The quick brown fox

--------------------------------------------------------

B. After adding an Integer to the clone
  Contents of "original":
   0 (java.lang.StringBuffer): The quick brown fox

  Contents of "clone":
   0 (java.lang.StringBuffer): The quick brown fox
   1 (java.lang.Integer): 5

--------------------------------------------------------

C. After modifying one of original's elements
  Contents of "original":
   0 (java.lang.StringBuffer): The quick brown fox jumps over the lazy dog.

  Contents of "clone":
   0 (java.lang.StringBuffer): The quick brown fox jumps over the lazy dog.
   1 (java.lang.Integer): 5

--------------------------------------------------------

運行結果打印出3個區域塊,在第一個區域塊中成功的創建了一個對象和復制拷貝了一個一模一樣的對象。從第二個區域塊可以看出兩個對象是相互對立的,當往復制拷貝的對象添加一個 Integer 數值,打印的結果顯示原有的對象不受影響,復制拷貝的對象打印添加的數值。第三個區域塊修改了 StringBuffer 的值,打印顯示出兩個對象里面的內容同時被修改了,這恰恰驗證了上面的說法,應用類型的值時用 clone 時無法被完全復制過去的。

所以使用 clone 這種方法復制內容可能會造成不可預測的錯誤。

為了解決淺拷貝帶來的問題,另外的解決方案是使用深度拷貝(deep copy of object) 。深度拷貝使得原有對象和新對象完全獨立,對象里面的所有屬性都完成被復制過去。Java Api 提供的 clone 方法無法一步到位的深度拷貝,只能為對象里面的所有的熟悉均實現 Cloneable 接口,為所有的屬性變量 clone 一遍。就如同上的例子,需要重寫 clone 方法拷貝一個新的字符串,再把新的字符串對象復制給 Vector克隆出來的對象。
不過這種做法有幾點缺點:

  1. 要拷貝一個對象,必須修改這個類里面所有元素的 clone 方法。假如你使用第三庫的代碼,而且沒有源碼這就無法使用這個方法,還有如果使用 final 修飾變量這也是沒使用這個拷貝方法
  2. 要有權限訪問父類屬性變量,如果某個變量是私有屬性就沒有權限修改它
  3. 必須要確定屬性變量的類型,尤其不能運行中才能確定的類型
  4. 操作繁瑣容易出錯

另外一種通用的辦法就是使用 Java Object Serialization (JOS) ,使用序列化,使用把 ObjectOutputStream 對象寫入出入到一個數組中,再使用 ObjectInputStream 復制到另一個對象,從而得到兩個完全獨立的對象。

import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;

/**
 * Utility for making deep copies (vs. clone()'s shallow copies) of 
 * objects. Objects are first serialized and then deserialized. Error
 * checking is fairly minimal in this implementation. If an object is
 * encountered that cannot be serialized (or that references an object
 * that cannot be serialized) an error is printed to System.err and
 * null is returned. Depending on your specific application, it might
 * make more sense to have copy(...) re-throw the exception.
 *
 * A later version of this class includes some minor optimizations.
 */
public class UnoptimizedDeepCopy {

    /**
     * Returns a copy of the object, or null if the object cannot
     * be serialized.
     */
    public static Object copy(Object orig) {
        Object obj = null;
        try {
            // Write the object out to a byte array
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(orig);
            out.flush();
            out.close();

            // Make an input stream from the byte array and read
            // a copy of the object back in.
            ObjectInputStream in = new ObjectInputStream(
                new ByteArrayInputStream(bos.toByteArray()));
            obj = in.readObject();
        }
        catch(IOException e) {
            e.printStackTrace();
        }-快速深度拷貝一個對象
        catch(ClassNotFoundException cnfe) {
            cnfe.printStackTrace();
        }
        return obj;
    }

}


這種方法也是有缺點的:

  1. 必須序列化,都要實現 java.io.Serializable 接口
  2. 序列化影響程序運輸速度。
  3. 可以適應不同的對象大小,創建適合大小的數組,可以在多線程任務中安全使用。符合上面種種特點,可想而知速度是有多慢。

出于以上的種種缺點,可以對 ByteArrayOutputStream 和 ByteArrayInputStream 做出3點優化:

  1. ByteArrayOutputStream 默認初始化 32 byte的數組,遇到大的對象就增大為目前兩倍大的數組,這意味著要摒棄到初始化的小數組,優化這個問題的方法也簡單,初始化一個大的數組。
  2. ByteArrayOutputStream 修改的內容都是 synchronized 修飾的,這是個安全的做法,但是在確定的單一線程里許可以拋棄 synchronized 這樣做也是安全的。
  3. toByteArray() 方法會創建和復制一個字節流數組。檢索數據數組然后繼續寫入流中,原有的數據就不會被改變。盡管如此,這循環復制一個數組會造成多余的垃圾回收工作。

深度拷貝速度優化

FastByteArrayOutputStream.class

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;

/**
 * ByteArrayOutputStream implementation that doesn't synchronize methods
 * and doesn't copy the data on toByteArray().
 */
public class FastByteArrayOutputStream extends OutputStream {
    /**
     * Buffer and size
     */
    protected byte[] buf = null;
    protected int size = 0;

    /**
     * Constructs a stream with buffer capacity size 5K 
     */
    public FastByteArrayOutputStream() {
        this(5 * 1024);
    }

    /**
     * Constructs a stream with the given initial size
     */
    public FastByteArrayOutputStream(int initSize) {
        this.size = 0;
        this.buf = new byte[initSize];
    }

    /**
     * Ensures that we have a large enough buffer for the given size.
     */
    private void verifyBufferSize(int sz) {
        if (sz > buf.length) {
            byte[] old = buf;
            buf = new byte[Math.max(sz, 2 * buf.length )];
            System.arraycopy(old, 0, buf, 0, old.length);
            old = null;
        }
    }

    public int getSize() {
        return size;
    }

    /**
     * Returns the byte array containing the written data. Note that this
     * array will almost always be larger than the amount of data actually
     * written.
     */
    public byte[] getByteArray() {
        return buf;
    }

    public final void write(byte b[]) {
        verifyBufferSize(size + b.length);
        System.arraycopy(b, 0, buf, size, b.length);
        size += b.length;
    }

    public final void write(byte b[], int off, int len) {
        verifyBufferSize(size + len);
        System.arraycopy(b, off, buf, size, len);
        size += len;
    }

    public final void write(int b) {
        verifyBufferSize(size + 1);
        buf[size++] = (byte) b;
    }

    public void reset() {
        size = 0;
    }

    /**
     * Returns a ByteArrayInputStream for reading back the written data
     */
    public InputStream getInputStream() {
        return new FastByteArrayInputStream(buf, size);
    }

}

ByteArrayInputStream.class

import java.io.InputStream;
import java.io.IOException;

/**
 * ByteArrayInputStream implementation that does not synchronize methods.
 */
public class FastByteArrayInputStream extends InputStream {
    /**
     * Our byte buffer
     */
    protected byte[] buf = null;

    /**
     * Number of bytes that we can read from the buffer
     */
    protected int count = 0;

    /**
     * Number of bytes that have been read from the buffer
     */
    protected int pos = 0;

    public FastByteArrayInputStream(byte[] buf, int count) {
        this.buf = buf;
        this.count = count;
    }

    public final int available() {
        return count - pos;
    }

    public final int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }

    public final int read(byte[] b, int off, int len) {
        if (pos >= count)
            return -1;

        if ((pos + len) > count)
            len = (count - pos);

        System.arraycopy(buf, pos, b, off, len);
        pos += len;
        return len;
    }

    public final long skip(long n) {
        if ((pos + n) > count)
            n = count - pos;
        if (n < 0)
            return 0;
        pos += n;
        return n;
    }

}

ByteArrayInputStream 和 ByteArrayOutputStream 的使用

import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;

/**
 * Utility for making deep copies (vs. clone()'s shallow copies) of 
 * objects. Objects are first serialized and then deserialized. Error
 * checking is fairly minimal in this implementation. If an object is
 * encountered that cannot be serialized (or that references an object
 * that cannot be serialized) an error is printed to System.err and
 * null is returned. Depending on your specific application, it might
 * make more sense to have copy(...) re-throw the exception.
 */
public class DeepCopy {

    /**
     * Returns a copy of the object, or null if the object cannot
     * be serialized.
     */
    public static Object copy(Object orig) {
        Object obj = null;
        try {
            // Write the object out to a byte array
            FastByteArrayOutputStream fbos = 
                    new FastByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(fbos);
            out.writeObject(orig);
            out.flush();
            out.close();

            // Retrieve an input stream from the byte array and read
            // a copy of the object back in. 
            ObjectInputStream in = 
                new ObjectInputStream(fbos.getInputStream());
            obj = in.readObject();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
        catch(ClassNotFoundException cnfe) {
            cnfe.printStackTrace();
        }
        return obj;
    }

}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java對象的Copy 引用CSDN博客地址:http://m.blog.csdn.net/chenssy/art...
    佩佩691閱讀 916評論 0 0
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399
  • 氣溫的不斷下降,全國各地也都開始飄雪,但是我們處在這個不南不北的城市,卻始終平靜,有些來自南方的同學迫不及待的想要...
    尋找阿水閱讀 246評論 1 0
  • 自從看了《朗讀者》之后就感慨很多 尤其是陸川在讀了一篇“藏羚羊的跪拜” 萬物皆是生靈 大自然賦予我們能力 就盡量不...
    我是一只愛發呆的懶貓閱讀 249評論 0 0
  • 有些人,似假茶,不必在意 有些人,如新茶,可以回味 有些人,像熟茶,可以暖心 有些人,是老茶,值得感悟 心放下了,...
    云上文化閱讀 306評論 0 0