CopyOnWriteArrayList源码阅读-- java bug 6260652

文章目录

  • 背景
  • 如何查看jdk bug
  • bug原因
  • 是谁的问题
  • 拓展

背景

在看CopyOnWriteArrayList源码时发现里边有一段注释,说明有bug并进行了修改,6260652是bugid

public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

如何查看jdk bug

jdk的bug都记录在了自己的数据库中,可以在网站上用bugid查询
链接:https://bugs.java.com/bugdatabase/

bug原因

可以直接看jdk bug网站上的介绍,链接
简单的说,就是代码里用到的Collection.toArray方法返回的结果预期是Object[],但有Collection的子类在实现此方法的时候返回的结果并不是Object[]。
这个类就是Arrays.ArrayList,其代码如下,它的toArray方法是使用的clone,返回的真实类型其实是E[]

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

		@Override
        public Object[] toArray() {
            return a.clone();
        }
        
        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
        ...
}

如果直接拿到这个数组使用,往里放入非E类型的其他Object,就会出现异常。

是谁的问题

其实是多种问题一起触发了最终的错误。

  1. Arrays.ArrayList的问题,这个类实现toArray的时候,违背了里氏替换原则,重写了的toArray方法和父类的表现不一致。而里氏替换原则要求父类只要能出现的地方,子类出现也不会报错。
  2. CopyOnWriteArrayList使用了Collection.toArray,对其有注释外的预期,当有不符合预期的子类实现时,会出现错误。这也是为什么程序要满足里氏替换原则,就是为了避免这种问题出现
  3. java的“假泛型”实现,java的泛型其实只是一个类型检查,在编译过程中有可能会无法真正识别泛型的类型。而这就导致一些问题只能到执行过程中才能发现。至于java为什么采用“假泛型”实现,其实实现真泛型不会更难,只是为了向前兼容才这么做,而向前兼容对于一门语言来说是至关重要的。

拓展

  1. Arrays.ArrayList的元素存储用的是E[],而java.util.ArrayList的元素存储用的是Object[],这是为什么?
    答:(1)因为泛型类型在编译的时候无法实例化对象,原因是java对泛型进行了擦除,所以只能用覆盖范围最广的Object[];而Arrays.ArrayList里的数组不是自己初始化的,是从外界传入的,不存在实例化的问题,可以使用明确的类型声明,当然其本质也是Object[],只是会做一个类型强转。
    参考链接

你可能感兴趣的:(java,bug,开发语言,ArrayList,里氏替换原则)