對象的拷貝
深度拷貝一個對象
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克隆出來的對象。
不過這種做法有幾點缺點:
- 要拷貝一個對象,必須修改這個類里面所有元素的 clone 方法。假如你使用第三庫的代碼,而且沒有源碼這就無法使用這個方法,還有如果使用 final 修飾變量這也是沒使用這個拷貝方法
- 要有權限訪問父類屬性變量,如果某個變量是私有屬性就沒有權限修改它
- 必須要確定屬性變量的類型,尤其不能運行中才能確定的類型
- 操作繁瑣容易出錯
另外一種通用的辦法就是使用 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;
}
}
這種方法也是有缺點的:
- 必須序列化,都要實現 java.io.Serializable 接口
- 序列化影響程序運輸速度。
- 可以適應不同的對象大小,創建適合大小的數組,可以在多線程任務中安全使用。符合上面種種特點,可想而知速度是有多慢。
出于以上的種種缺點,可以對 ByteArrayOutputStream 和 ByteArrayInputStream 做出3點優化:
- ByteArrayOutputStream 默認初始化 32 byte的數組,遇到大的對象就增大為目前兩倍大的數組,這意味著要摒棄到初始化的小數組,優化這個問題的方法也簡單,初始化一個大的數組。
- ByteArrayOutputStream 修改的內容都是 synchronized 修飾的,這是個安全的做法,但是在確定的單一線程里許可以拋棄 synchronized 這樣做也是安全的。
- 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;
}
}