July 17, 2017 Update
關于克隆,深拷貝淺拷貝,今天又看了一下這個文章clone方法 --深拷貝與淺拷貝,寫得非常通俗易懂,我看了前半部分,終于是知道了shallow copy的意思。。后半部分希望以后有機會看看(不知猴年馬月了。。)。
先學習一下克隆。
Fantastic chance to remember a thing! 之前寫算法一直被ArrayList的傳遞confuse,常見的場景是一個ArrayList<ArrayList<Integer>> result;
保存著很多cell: ArrayList<Integer> cell;
但我每次result.add(cell);
的時候,之前add進來的cell也跟著變了,所以要每次add之前要new一個ArrayList,比如result.add(new ArrayList<Integer>(cell));
這樣才行。
為什么會這樣?因為:
Java中,在任何用"="向對象變量賦值的時候都是「引用傳遞」。
僅僅傳遞了「對象的引用」。就像傳遞了一個指針一樣,后續操作都是在原來的對象上操作的。另外一點,
Java中用對象的作為入口參數的傳遞則缺省為「引用傳遞」。
Object類的clone()最終調用的是native方法:
protected native Object clone() throws CloneNotSupportedException;
native方法的效率一般來說都是遠高于java中的非native方法。這也解釋了為 什么要用Object中clone()方法而不是先new一個類,然后把原始對象中的信息賦到新對象中,雖然這也實現了clone功能。
為什么必須implements Cloneable接口
如果覆寫clone,就必須implements Cloneable,否則會throw CloneNotSupportedException。算是一個約定。
//Object.java
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
return internalClone();
}
effective java說這是mixin接口。
Iteye的回答:
- Cloneable是標示接口與java.io.Serializable類似,用于告知JVM該對象實現clone。并且super.clone()可以返回一個復制。
- 很多時候,會把protected的clone方法修改為public,暴露給其他人可以使用。
淺拷貝和深拷貝
淺拷貝:拷貝基本成員屬性,對于引用類型僅返回指向改地址的引用。
2.28
FROM API:
The general intent is that, for any object x, the expression:
根據convention,這兩點是必須是true的:
x.clone() != x
x.clone().getClass() == x.getClass()
這一點也要是true,但不是必須:
x.clone().equals(x)
根據convention,返回的object應該通過super.clone獲取。如果一個類和它的superclasses(Object除外)遵循這個convention,那就有x.clone().getClass() == x.getClass().
通常,為了達到cloned objects是獨立的,需要修改super.clone返回的object中的1個或更多的fields,然后再返回(也就是深拷貝)。
如果一個class只包含primitive fields(基本類型)或者Immutable objects,super.clone返回的object中的fields就不需要再做修改。
FROM EFFECTIVE JAVA:
如果每個域包含一個基本類型的值,或者包含一個指向不可變對象的引用,那么被返回的對象則可能正式你需要的對象,在這種情況下就不需要再做處理。例如在第9條中的PhoneNumber類正是如此:
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix,
int lineNumber) {
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
}
那就只需要:
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch(CloneNotSupportedException e) {
throw new AssertionError();
}
}
這里clone方法返回的是PhoneNumber,而不是返回的Object。從Java1.5發行版本開始,這么做就是合法的,因為1.5發行版本引入了協變返回類型(convariant return type)作為泛型。體現了一條原則:永遠不要讓客戶去做任何類庫能夠替客戶完成的事情。
對于下面這樣的類就不能直接return super.clone了,size域中具有正確的值,但是它的elements域將引用與原Stack實例相同的數組。
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
publci Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (0 == size) throw new EmptyStackException();
return elements[--size];
}
/**
* Ensure space for at least one more element
* roughly doubling the capacity each time the
* array needs to grow
* /
private void ensureCapacity() {
if (size == elements.length)
elements = Arrays.copyOf(elements, 2* size + 1);
}
}
這時候需要深拷貝了,可以遞歸:
@Override
public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
或者bucket clone:
@Override
public HashTable clone() {
try {
HashTable result = (HashTable) super.clone();
result.buckets = buckets.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
這里不細談了,有點復雜,具體看書吧。
。。。
總之,deep copy很麻煩的樣子。書上說,最好不要覆蓋,也少去調用(因為完全可以new一個..),這里我就不深挖了。
See also:
http://blog.csdn.net/Jing_Unique_Da/article/details/49901933
http://lovelace.iteye.com/blog/182772
http://www.cnblogs.com/tonyluis/p/5778266.html
http://www.oschina.net/translate/java-copy-shallow-vs-deep-in-which-you-will-swim
https://zhidao.baidu.com/question/546603399.html