Item 11 Override clone judiciously

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的回答

  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

转载于:https://juejin.im/post/5a31341d6fb9a044fb07c302

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值