• Java


    覆盖clone时需要实现Cloneable接口,Cloneable并没有定义任何方法。
    那Cloneable的意义是什么?
    如果一个类实现了Clonable,Object的clone方法就可以返回该对象的逐域拷贝,否则会抛出CloneNotSupportedException


    通常,实现接口是为了表明类的行为。
    而Cloneable接口改变了超类中protected方法的行为。
    这是种非典型用法,不值得仿效。


    好了,既然覆盖了clone方法,我们需要遵守一些约定:

    • x.clone() != x;
    • x.clone().getClass() = x.getClass();
    • x.clone().equals(x);

    另外,我们必须保证clone结果不能影响原始对象的同时保证clone方法的约定。


    比如下面这种情况,没有覆盖clone方法,直接得到super.clone()的结果:

    import java.util.Arrays;
    
    public class Stack implements Cloneable {
        private Object[] elements;
        private int size = 0;
        private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
        public Stack() {
            this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
        }
    
        public void push(Object e) {
            ensureCapacity();
            elements[size++] = e;
        }
    
        public Object pop() {
            if (size == 0)
                throw new EmptyStackException();
            Object result = elements[--size];
            elements[size] = null; // Eliminate obsolete reference
            return result;
        }
    
        public boolean isEmpty() {
            return size == 0;
        }
    
        // Ensure space for at least one more element.
        private void ensureCapacity() {
            if (elements.length == size)
                elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    
    }
    


    结果可想而知,clone结果的elements和原始对象的elements引用同一个数组。

    既然如此,覆盖clone方法,并保证不会伤害到原始对象:

    @Override
    public Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
    

    虽然把elements单独拿出来clone了一遍,但这种做法的前提是elements不是final。
    其实再正常不过,clone无法和引用可变对象的不可变field兼容。


    如果数组的元素是引用类型,当某个元素发生改变时仍然会出现问题。
    此处以Hashtable为例,Hashtable中的元素用其内部类Entry。

    private static class Entry<K,V> implements Map.Entry<K,V> {
        int hash;
        final K key;
        V value;
        Entry<K,V> next;
    
        protected Entry(int hash, K key, V value, Entry<K,V> next) {
            this.hash = hash;
            this.key =  key;
            this.value = value;
            this.next = next;
        }
    
        //..
    }
    


    如果像Stack例子中那样直接对elements进行clone,某个Entry发生变化时clone出来的Hashtable也随之发生变化。

    于是Hashtable中如此覆盖clone:

    /**
     * Creates a shallow copy of this hashtable. All the structure of the
     * hashtable itself is copied, but the keys and values are not cloned.
     * This is a relatively expensive operation.
     *
     * @return  a clone of the hashtable
     */
    public synchronized Object clone() {
        try {
            Hashtable<K,V> t = (Hashtable<K,V>) super.clone();
            t.table = new Entry[table.length];
            for (int i = table.length ; i-- > 0 ; ) {
                t.table[i] = (table[i] != null)
                    ? (Entry<K,V>) table[i].clone() : null;
            }
            t.keySet = null;
            t.entrySet = null;
            t.values = null;
            t.modCount = 0;
            return t;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }
    


    鉴于clone会导致诸多问题,有两点建议:

    • 不要扩展Cloneable接口
    • 为继承而设计的类不要实现Cloneable接口
  • 相关阅读:
    Struts2学习笔记《三》
    《Shiro框架》shiro学习中报错解决方法
    android
    MAC 设置环境变量path的几种方法
    利用ant脚本 自动构建svn增量/全量 系统程序升级包
    Jenkins2 插件 Pipeline+BlueOcean 实现持续交付的初次演练
    Jenkins2 实现持续交付初次演练(MultiJob,Pipeline,Blue Ocean)
    jenkins2 -pipeline 常用groovy脚本
    jenkins2 pipeline介绍
    scala学习(1)----map和flatMap的区别
  • 原文地址:https://www.cnblogs.com/kavlez/p/4194472.html
Copyright © 2020-2023  润新知