Item 11: Override clone judiciously

July 17, 2017 Update

关于克隆,深拷贝浅拷贝,今天又看了一下这个文章clone方法 --深拷贝与浅拷贝,写得非常通俗易懂,我看了前半部分,终于是知道了shallow copy的意思。。后半部分希望以后有机会看看(不知猴年马月了。。)。


先学习一下克隆。
Fantastic chance to remember a thing! 之前写算法一直被ArrayList的传递confuse,常见的场景是一个ArrayList> result;保存着很多cell: ArrayList cell;但我每次result.add(cell);的时候,之前add进来的cell也跟着变了,所以要每次add之前要new一个ArrayList,比如result.add(new ArrayList(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的回答:

  1. Cloneable是标示接口与java.io.Serializable类似,用于告知JVM该对象实现clone。并且super.clone()可以返回一个复制。
  1. 很多时候,会把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

你可能感兴趣的:(Item 11: Override clone judiciously)