July 17, 2017 Update
关于克隆,深拷贝浅拷贝,今天又看了一下这个文章clone方法 --深拷贝与浅拷贝,写得非常通俗易懂,我看了前半部分,终于是知道了shallow copy的意思。。后半部分希望以后有机会看看(不知猴年马月了。。)。
先学习一下克隆。
Fantastic chance to remember a thing! 之前写算法一直被ArrayList的传递confuse,常见的场景是一个ArrayList
保存着很多cell: ArrayList
但我每次result.add(cell);
的时候,之前add进来的cell也跟着变了,所以要每次add之前要new一个ArrayList,比如result.add(new ArrayList
这样才行。
为什么会这样?因为:
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