ArrayList源码解析(一)

以下分析均以jdk1.8为准。

首先来看一下ArrayList的继承体系:
ArrayList继承体系
ArrayList继承自AbstractList,实现了 List, Cloneable, Serializable, RandomAccess接口。这一点从源码上也可以看到

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

先来看看ArrayList的内部成员概览吧。
这里写图片描述
从上图看到ArrayList内部,有7个成员变量,4个内部类。

  private static final long serialVersionUID = 8683452581122892189L;   //用作序列化,暂不管
//默认初始化容量为10
    private static final int DEFAULT_CAPACITY = 10;

//空的对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

//默认的空对象数组,调用ArrayList的无参构造函数时创建的对象数组。                                   
    private static final Object[] EFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 实际存放List元素的对象数组缓存变量。ArrayList的容量就是这个数组的长度(length)。
    transient Object[] elementData;

//ArrayList的大小,实际所拥有元素的数量。
    private int size;

//最大数组容量,大约为2147483639。20多亿,绝对够用了
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

注意到elementData被transient所修饰。
有个关键字需要解释:transient。

Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
tansient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

带默认初始化容量的构造函数 ArrayList ( int initialCapacity )

public ArrayList(int initialCapacity) {
    //如果初始化容量大于0
    if (initialCapacity > 0) {
    //直接new一个该大小的对象数组赋值给elementData
           this.elementData = new Object[initialCapacity];
       } else if (initialCapacity == 0) {  //如果初始化容量为0
       //直接将空数组赋值给elementData
           this.elementData = EMPTY_ELEMENTDATA;
       } else {//初始化参数为负数,抛出异常,非法参数
            throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);
        }
    }

无参构造函数ArrayList( )

 public ArrayList() {
 //将空数组赋值给elementData,这时候容量还是0,之后添加元素时会扩容
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

通过指定的集合对象c来构造ArrayList( Collection< ? extends E  > c )

按照集合对象的迭代器返回的元素顺序进行填充。如果集合对象为null,抛出NullPointerException异常

 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//将集合c转换为对象数组再赋值给elementData。不同的Collection实现有各自不同的toArray()方法。
        if ((size = elementData.length) != 0) {//elementData大小不为0
            if (elementData.getClass() != Object[].class) //c.toArray()返回的不一定是Object[],这里进行一个类型转换
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //如果c.toArray()为空数组,elementData默认为空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

修整容量为当前实际大小: trimTosize( )。

(容量一般会由于扩容的原因大于实际大小,例如添加了一个元素的ArrayList实例容量为10,大小为1,该操作就是将容量缩小为1),该操作可以减少ArrayList的内存使用。但执行完trimTosize()方法之后如果再添加元素,还是会进行扩容。所以除非知道在之后不会再添加元素,否则调用trimTosize()方法没有意义。

public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size); //利用Arrays.copyOf()方法对数组进行复制重组
        }
    }

当前元素数量size( )

public int size() {
        return size;
    }

获取对象索引:indexOf( Object o )

返回对象第一次出现时的索引,如果没有该对象,返回-1。

 public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

首先判断传入对象o是否为null,如果是null,for循环遍历elementData,如果找到某个元素为null,则返回该元素的索引值。如果o不为null,for循环遍历elementData,通过equals判断如果有某个元素等于指定对象o,则返回该元素的索引。如果找不到该对象,返回-1。
这里通过对象的equals方法判断元素是否为某个对象。

获取最后一次出现的索引 lastIndexOf( Object o )


/**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     */

    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

逻辑跟indexOf类似,只不过从数组末尾向前遍历。

是否包含某元素:contains(&nsbsp;Object o )

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

通过判断对象索引是否>=0。

将list转换为对象数组 toArray()

新对象数组的长度为list的size

 public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

返回的是Object[],并不是对应的泛型数组,而且也不能强制转换为对应的泛型数组。如果需要转化为对于的泛型数组,可以参考 public < T > T[] toArray(T[] a) 方法。
举例说明:

 ArrayList<Integer> list = new ArrayList<Integer>();
        list.addAll(Arrays.asList(1,2,3,4,5,6,7,8,9));
        Object[] objects = list.toArray();
        Integer[] s = (Integer[])objects;  //抛出异常:
        //java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;

返回的数组是重现分配了内存的,跟原来的list已经脱离,相互不影响

将list转换为对应的泛型数组 toArray(T[] a)

/**    
     * @return an array containing the elements of the list
     * @throws ArrayStoreException if the runtime type of the specified array is
     *  not a supertype of the runtime type of every element in this list
//如果a的类型不是list中所有元素的超类,那么抛出ArrayStoreException异常。
public <T> T[] toArray(T[] a) {
        if (a.length < size)
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

从源码上看,当a.length < size时,创建一个新的数组并将elementData的数据复制到新数组中,返回新数组。这没有任何问题。

需要注意的是:当a.length>size时,将elementData的数据复制到a,将a[size]置为null。虽然不明白为什么要这么做,但需要注意。最终返回a。例如我写了下面一个测试例子,会导致一些预料之外的事情。

    List<Integer> list = new ArrayList<Integer>();
    list.addAll(Arrays.asList(1,2,3,4,5,6,7,8,9));
    Integer[] intArr = {1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5};  // intArr的长度大于list的size
    Integer[] result = list.toArray(intArr);
    for (int i = 0; i < intArr.length; i++) {
        System.out.print(intArr[i]+" ");  //输出1 2 3 4 5 6 7 8 9 null 4 4 5 5 5 . 
       //由于intArr[size] = null。 size为9的数据被置空了,这显然不是我们需要的。
    }
    //而且需要注意的是:intArr和返回值result指向了同一个数组对象,修改其中一个会影响另一个
    System.out.println();
    for (int i = 0; i < result.length; i++) {
        System.out.print(result [i]+" "); //输出1 2 3 4 5 6 7 8 9 null 4 4 5 5 5
    }
    System.out.println();
    result[0] = 0; result[1] = 0; //修改result,也会同步影响intArr。
    for (int i = 0; i < intArr.length; i++) {
        System.out.print(intArr[i]+" "); //输出0 0 3 4 5 6 7 8 9 null 4 4 5 5 5
    }

对此,我的建议是:传递给toArray(T[] a)的数组a为空数组(没有填充数据),只要指明相应的类型就可以。Integer[] intArr = list.toArray(new Integer[]{})

通过索引获取对象 E get(int index)

public E get(int index) {
     if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

     return (E)elementData(index);
    }

设置指定索引的值

 public E set(int index, E element) {
      if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

      E oldValue = elementData(index);
      elementData[index] = element;
      return oldValue;
   }

清空list: clear()

public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

clear()只是将元素都置为null,将size设置为0。但不会修改容量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值