zhizhizhiyuan

由ArrayList构造函数源码引出的问题

  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;

posted on 2014-04-13 16:14  zhizhizhiyuan  阅读(982)  评论(1编辑  收藏  举报

导航