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