• 由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;

  • 相关阅读:
    代码
    (转载)计算机的二进制起源
    表的新建
    SQL约束
    包装类
    GUID(转载)
    Android九宫格解锁自定义控件(附源码)
    Android滑动页面返回(自定义控件)
    Android高仿QQ消息滑动删除(附源码)
    Android跟踪球-手势移动图片-自定义控件(附源码)
  • 原文地址:https://www.cnblogs.com/zhizhizhiyuan/p/3662371.html
Copyright © 2020-2023  润新知