ArrayList应该用得很多了。最近看了看其源码,发现有很多细节,如果要我们自己来实现,估计会考虑不到。当然,这些细节跟jdk本身一些实现的bug有关,如果不去深挖,定然是不能发现。本文从ArrayList的一个构造函数开始剖析。
该构造函数源代码如下:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
与上述源代码相关的elementData的申明如下:
private transient Object[] elementData;
注意到,这是一个Object类型的数组。
我的疑问在于,第四行注释是什么意思?原来,它表明这里涉及到jdk的一个bug,代号6260652。经过查找,在http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652中有描述这个bug。它是这么描述的:Arrays.asList(x).toArray().getClass() should be Object[].class。原来,bug由来于Arrays这个工具类。查看源代码:
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
这边的ArrayList并非我们常用的java.util.ArrayList,而是Arrays的内部类。它继承自AbstractList,自然实现了Collection接口,代码如下:
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) {
if (array==null)
throw new NullPointerException();
a = array;
} public int size() {
return a.length;
}
。。。。。。
}
可以发现,这里的a不是 Object[],而是E[]。a称为该ArrayList的backed array。同时构造函数也是直接用array给a赋值。这就是问题的所在。举个例子:
String[] s=new String[]{"hello","world"};
List<String> list=Arrays.asList(s);
Object[] a=list.toArray();
System.out.println(a.getClass().getName());
上述代码输出的是"[Ljava.lang.String"。说明是String[]。而另一段代码(如下)输出的则是"[Ljava.lang.Object"。
ArrayList<String> s=new ArrayList<String>();
System.out.println(s.toArray().getClass().getName());
出于可靠性考虑,需要保证java.util.ArrayList的backed array类型都是Object[]的。故而,文章第一段代码处需要做一个判断,如果参数的toArray得到的类型不是Object[],则做另外的处理。如果不做该处理,会有问题吗?假如有如下代码:
List<Object> l = new ArrayList<Object>(Arrays.asList("foo", "bar"));
l.set(0, new Object());
如果不做处理,则会出现ArrayStoreException。因为l中的array类型是String[],不能随意存入Object类型的数据。通过该处理,就把Array.asList的问题在这个地方消除了。
另一个问题:
由此也更容易理解另一个问题,就是ArrayList的toArray。该函数有两种实现方式:
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
} public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
通常我们使用第二种,如果我们有一个ArrayList<String> s,要把它转换成String[],我们要这样写:
String[] sa=s.toArray(new String[0]);
第二中返回的是泛型的数组,第一种则是Object[]。如果使用第一种,我们就不便对数组的值进行更改了,因为数组元素的类型是Object,而不是String。如果强制转成String[],则会出现ClassCastException。
总结:ArrayList中的elementData是Object[],由此带来的问题,也应当在使用的时候注意。
private transient Object[] elementData;