【JDK源码】ArrayList中的一个Bug分析(id=6260652)

64人浏览 / 0人评论

引言

首先看一段代码, 这段代码是ArrayList的源码:

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

从代码中可以看到有一行注释:// c.toArray might (incorrectly) not return Object[] (see 6260652)。看到这一行注释有些懵圈,首先elementData = c.toArray()这一行代码没有报错,那么也就是说使用Obect[]接收这个数组是没有问题的,那么为什么又会出现下面当数据不为空的时候还要判断类型,然后再重新设值呢?从注释中可以看到6260652这个编号,这是oracle java 官方的bug数据库中的bug编号,也就是说这个问题是Java本身的一个bug,通过查看文档,在Java 9b73中得到了修复。

Java Bug数据库描述

完整产品编号

- java: 1.5.0_02
- Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_02-09)
- Java HotSpot(TM) Client VM (build 1.5.0_02_b09, mixed mode, sharing)

操作系统版本信息

- Linux itppc27 2.4.19-4GB #1 Mon Aug 4 23.38.42 UTC 2003 i686 unknown

问题描述

Collection文档中声明到:collection.toArray()collection.toArray(new Object[0])在功能上完全相同。

但是, Arrays.ArrayList并没有遵守该规则进行实现。使用子类型数组(如String[])创建List,则toArray()方法将返回子类型的数组(String[], 因为底层采用的是clone()方法实现),而不是Object[]数组。

如果之后想要在该数组中存储非子类型(String)对象,则会抛出ArrayStoreException异常。

所以,Arrays.asList()的实现(返回元素类型而不是Object)或者Collection toArray的文档(没有参数的toArray()返回类型不能是子类型)是错的。

使用以下代码可以重现这个问题:

import java.util.*;


public class ToArray
{
    public static void main(String[] args)
    {
        List l = Arrays.asList(args);

        System.out.println(l.toArray());
        System.out.println(l.toArray(new Object[0]));
    }
}

期待输出:

[Ljava.lang.Object;@10b62c9
[Ljava.lang.Object;@82ba41

实际输出:

[Ljava.lang.String;@10b62c9
[Ljava.lang.Object;@82ba41

Arrays.ArrayList.toArray() VS ArrayList.toArray()

  • Arrays.ArrayList.toArray()源码:
    @Override
    public Object[] toArray() {
        return a.clone();
    }
  • ArrayList.toArray()源码:
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
  • Arrays.copyOf(elementData, size);
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

从copyOf(elementData, size)的源码中可以看出,其返回的数组类型,其实是由数组类型决定(强转)的。

因此我有有些好奇,这两个类中的elementData的类型是什么。

  • Arrays.ArrayList中
private final E[] a;
  • ArrayList中
transient Object[] elementData; // non-private to simplify nested class access

由此可以看出 Arrays中使用了泛型, 在看看 asList(T... t)方法的源码

    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

由此可以看出这个泛型取决于源数组元素类型。

那么, 我就开始思考,如果我把这个E[] e直接声明称Object[] e是否就可以解决这个问题呢?

于是,编写了一个新的ArrayList, 代码与Arrays.ArrayList大部分相同,只修改e的声明方式(仅保留关键方法):

package com.thunisoft.commons.arrays;

import java.util.*;

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

    MyArrayList(E[] array) {
        a = array;
    }

    @Override
    public Object[] toArray() {
        return a.clone();
    }

    public static void main(String[] args) {
        MyArrayList<String> strings = new MyArrayList<String>(args);
        System.out.println(strings.toArray().getClass());
        System.out.println(Arrays.asList(args).toArray().getClass());
        java.util.ArrayList<String> arrayList = new java.util.ArrayList<String>(Arrays.asList(args));
        System.out.println(arrayList.toArray().getClass());
    }
}

输出:

class [Ljava.lang.String;
class [Ljava.lang.String;
class [Ljava.lang.Object;

事实证明,我的猜想是不成立的。那么问题的根本还就是处在:clonecopy的具体实现上了。

由于clone方法已经是native级别的方法了,不能继续深入了。所以我又看了copyOf(original, newLength, original.getClass())的源码:

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

方法中判断了新数组类型是否是Object[]类型, 如果不是, 则根据传入数组类型的元素类型创建数组。而ArrayList原数组类型都是Object[]

    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

ArrayList提供三个构造方法,前两个都使用内部常量对elementData初始化,类型为Object[].class。只有第三种方法可能由Arrays.ArrayList.toArray()初始化,也就有可能不是Object[].class

Java 9b73中的修复方案

Arrays.ArrayList.toArray()将一直返回Object[], 所以也将引发新的问题。

List<String> list = Arrays.asList("a", "b", "c");
String[] array = (String[]) list.toArray();

以前上述代码是可以正常运行的,但是修复后将抛出ClassCastException异常。

要实现以上功能,请使用以下代码:

String[] array = list.toArray(new String[0]);

关于具体如何修复该bug, 则需要看源码的实现。刚装了一个jdk-9.0.4,看了一下Arrays.ArrayList.toArray()的源码,果然是将clone机制改为copy机制。

@Override
public Object[] toArray() {
    return Arrays.copyOf(a, a.length, Object[].class);
}

思考

这个bug的出现除了在代码设计上考虑不周全,从面向对象的设计思想上看问题出在什么地方呢?

全部评论