前言

                  上一篇文章探究了Collection接口的详细设计和相关的方法。文章的链接如下:

            ,接下来将会探究其子接口List的源码,通过

             源码探究其接口设计和特点。

        List接口设计

                  通过查阅源码,我们知道的是List接口继承至Collection接口,并且实现了集合中元素的有序存

              储。也就是说用户可以对列表中的每个元素的插入位置进行精确的控制,可以根据元素在集合中

              的索引对元素进行访问和遍历(类似数组)。

        List接口源代码解析

                 对于List接口的设计,我们直接看源码和API来的更加实际一些。       

package com.kiritor;  import java.util.Iterator; import java.util.ListIterator;  /**  * List源码探究  * @author Kiritor*/ public interface List<E> extends Collection<E> {     /**List作为Collection的子接口      * 提供了Collection接口定义的方法      * 这些方法在Collection源码学习中      * 已经分析过了,就不在说明了      * */     int size();     boolean isEmpty();     boolean contains(Object o);     Iterator<E> iterator();     Object[] toArray();     <T> T[] toArray(T[] a);     boolean add(E e);     boolean remove(Object o);     boolean containsAll(Collection<?> c);     boolean addAll(Collection<? extends E> c);     boolean addAll(int index, Collection<? extends E> c);     boolean removeAll(Collection<?> c);     boolean retainAll(Collection<?> c);     void clear();     boolean equals(Object o);     int hashCode();     /**同时List接口定义了一些自己的方法      * 来实现“有序”这一功能特点*/     /**      *返回列表中指定索引的元素      *return E      *throws IndexOutofBoundException(index<0||index>=size())      * */     E get(int index);     /**      *设定某个列表索引的值       *throws:      *UnsupportedOperationException        ClassCastException        NullPointerException        IllegalArgumentException        IndexOutOfBoundsException       * */     E set(int index, E element);     /**      *在指定位置插入元素,当前元素和后续元素后移      *这是可选操作,类似于顺序表的插入操作 */     void add(int index, E element);     /**      * 删除指定位置的元素(可选操作)*/     E remove(int index);     /**      * 获得指定对象的最小索引位置,      * 没有则返回-1*/     int indexOf(Object o);     /**获得指定对象的最大索引位置      * 可以知道的是若集合中无给定元素的重复元素下      * 其和indexOf返回值是一样的*/     int lastIndexOf(Object o);     /**一种更加强大的迭代器,支持向前遍历,向后遍历      * 插入删除操作,此处不解释*/     ListIterator<E> listIterator();     ListIterator<E> listIterator(int index);     /**      * 返回索引fromIndex(包括)和toIndex(不包括)      * 之间的视图。*/     List<E> subList(int fromIndex, int toIndex); }

         ArrayList类实现

                    对于ArrayList类的实现笔者就不将代码一一贴出来了,这里只是简要的对一些个方法的

              实现做一些分析。首先我们看看ArrayList类头吧       

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

               可以看出的是他实现了一些额外的接口RandomAccess、Cloneable、Serializable


RandomAccess:List实现使用的标记接口,主要用于提高连续或随机访问列表时的性能。

   Cloneable:实现该接口的类可以对该类的实例进行克隆(按字段进行复制)

Serializable:用于标识可序列化(何为序列化?为什么要序列化,有待总结)

              1、底层实现

               通过对其源码的观察,可以发现的是ArrayList的底层是通过数组来实现的。

private transient Object[] elementData;             private int size;

               这里解释一下

transient关键字 :

                          Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的


                      对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭

                      serialization,可以在这个域前加上关键字transient。         

             2、构造方法

                     通过查看源码,我们可以知道的是ArrayList提供三种构造方法

public ArrayList(int initialCapacity) {         super();         if (initialCapacity < 0)             throw new IllegalArgumentException("Illegal Capacity: "+                                                initialCapacity);         this.elementData = new Object[initialCapacity];     }     public ArrayList() {         this(10);     }      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);     }

                       前两种构造方法很容易理解,根据提供的参数来进行数组空间分配,默认大小为10,


             第三种构造则显得稍微复杂些,它是将给定集合转换成数组(按照集合本身的顺序),如果

             转化成的emlementData不是Object[]类型则调用Arrays.copyOf方法将其转换为Object[]。

Tips:需要特别注意前两个构造方法和第三个构造方法的区别,前两者生成的集合是

             无元素的,代码中也没有对size进行修改的地方,第三个构造器对size是有修改的。

          3、存储元素

                       ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、

               addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加

               元素的方法。下面我们一一讲解:

/*  * 用指定的元素代替集合中指定位置的元素  * 并返回以前位于该位置的  */  public E set(int index, E element) {         rangeCheck(index);          E oldValue = elementData(index);         elementData[index] = element;         return oldValue;     }

set方法会通过rangeCheck()函数检查index的返回,若index>=size则会主动抛出
       IndexOutOfBoundsException异常,不过需要注意的是对于index<0的情况并不会主动抛出
       该异常,且两种情况抛出的异常详细信息不同(如何不同读者可自己尝试),这对于我们以后
       ArrayList下表越界错误的查找也算有一定的帮助吧,助于说为什么JDK对于该方法为什么
       不将index<0情况考虑进去,可能是因为对于数组越界的问题,我们更加关心的是“向前越界”

// 将指定的元素添加到此列表的尾部。       public boolean add(E e) {           ensureCapacity(size + 1); //该方法用于调整数组的容量           elementData[size++] = e;           return true;       }

                ensureCapacity(size+1)这个方法主要用于数组容量的扩容的,至于如何实现,具体后面


           会详细分析。

// 将指定的元素插入此列表中的指定位置。       // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。       public void add(int index, E element) {           if (index > size || index < 0)               throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);           // 如果数组长度不足,将进行扩容。           ensureCapacity(size+1);  // Increments modCount!!           // 将 elementData中从Index位置开始、长度为size-index的元素,           // 拷贝到从下标为index+1位置开始的新的elementData数组中。           // 即将当前位于该位置的元素以及所有后续元素右移一个位置。           System.arraycopy(elementData, index, elementData, index + 1, size - index);           elementData[index] = element;           size++;       }
// 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。       public boolean addAll(Collection<? extends E> c) {           Object[] a = c.toArray();           int numNew = a.length;           ensureCapacity(size + numNew);  // Increments modCount           System.arraycopy(a, 0, elementData, size, numNew);           size += numNew;           return numNew != 0;       }



// 从指定的位置开始,将指定collection中的所有元素插入到此列表中。       public boolean addAll(int index, Collection<? extends E> c) {           if (index > size || index < 0)               throw new IndexOutOfBoundsException(                   "Index: " + index + ", Size: " + size);                  Object[] a = c.toArray();           int numNew = a.length;           ensureCapacity(size + numNew);  // Increments modCount                  int numMoved = size - index;           if (numMoved > 0)               System.arraycopy(elementData, index, elementData, index + numNew, numMoved);                  System.arraycopy(a, 0, elementData, index, numNew);           size += numNew;           return numNew != 0;       }

           4、读取元素

//返回列表中指定位置的元素,需边界检查     public E get(int index) {         rangeCheck(index);          return elementData(index);     }

           5、删除元素

                     ArrayList提供了根据下标或者指定对象两种方式来删除元素代码如下:


// 移除此列表中指定位置上的元素,并返回,需边界转换       public E remove(int index) {           RangeCheck(index);                  modCount++;           E oldValue = (E) elementData[index];                  int numMoved = size - index - 1;           if (numMoved > 0)               System.arraycopy(elementData, index+1, elementData, index, numMoved);           elementData[--size] = null; // Let gc do its work                  return oldValue;       }
// 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。      //该方法通过对象删除,不许进行边界检查      public boolean remove(Object o) {           // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。           if (o == null) {               for (int index = 0; index < size; index++)                   if (elementData[index] == null) {                       // 类似remove(int index),移除列表中指定位置上的元素。                       fastRemove(index);                       return true;                   }       } else {           for (int index = 0; index < size; index++)               if (o.equals(elementData[index])) {                   fastRemove(index);                   return true;               }           }           return false;       }

                   第二个方法牵扯到了ArrayList中的一个私有的方法,该方法和remove(int index)方法


          类似,但是并不需要进行边界检查(通过remove(Object o)已经可以index的合理性了),容易

          明白的是该方法不应该直接调用,因此设定为private的,对象引用无法直接调用它,安全性得以

          保障。

private void fastRemove(int index) {         modCount++;         int numMoved = size - index - 1;         if (numMoved > 0)             System.arraycopy(elementData, index+1, elementData, index,                              numMoved);         elementData[--size] = null; // Let gc do its work     }

                 Tips:从数组中移除一个元素也会导致后面的元素左移。


            6、调整容量

                   很久以前我们就知道ArrayList等集合都是动态的,那么这种动态是如何来实现的?通过


            查阅源码,这种动态性的实现主要有下面两个方法实现的。

public void ensureCapacity(int minCapacity) {         if (minCapacity > 0)             ensureCapacityInternal(minCapacity);      }      private void ensureCapacityInternal(int minCapacity) {         modCount++;         // overflow-conscious code         if (minCapacity - elementData.length > 0)             grow(minCapacity);     }

               其工作过程以add方法为例

// 将指定的元素添加到此列表的尾部。       public boolean add(E e) {           ensureCapacity(size + 1); //该方法用于调整数组的容量           elementData[size++] = e;           return true;       }

                 在进行元素尾部插入的时候,首先确保插入元素后集合的size也就是minCapacity必须<=

           集合的容量,也就是elementData.lentgh,如果大于集合的容量之后则需通过grow方法

50%扩容的。

private void grow(int minCapacity) {         // overflow-conscious code         int oldCapacity = elementData.length;         int newCapacity = oldCapacity + (oldCapacity >> 1);         if (newCapacity - minCapacity < 0)             newCapacity = minCapacity;         if (newCapacity - MAX_ARRAY_SIZE > 0)             newCapacity = hugeCapacity(minCapacity);         // minCapacity is usually close to size, so this is a win:         elementData = Arrays.copyOf(elementData, newCapacity);     }

                   

思考:ArrayList进行扩容的时候,会将就数据中的元素重新拷贝一份到新的数组中去,这种

              代价是挺高的,因此我们应该尽量避免数组的扩容。而且需注意一点的是ensureCapacity()方法

              是public的,我们可以通过它手动的增加容量。

              而且ArrayList提供了一个有趣的方法,它可以将底层数组的容量调整为集合中实际元素的数量



public void trimToSize() {           modCount++;           int oldCapacity = elementData.length;           if (size < oldCapacity) {               elementData = Arrays.copyOf(elementData, size);           }       }

            7、Fail-Fast(快速失败)机制

                        仔细观察上述的各个方法,我们在源码中就会发现一个特别的属性modCount,API解释如下:
                 The number of times this list has been structurally modified. Structural modifications are those
                 that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress
                 may yield incorrect results.
                      记录修改此列表的次数:包括改变列表的结构,改变列表的大小,打乱列表的顺序等使正在进行
                 迭代产生错误的结果。Tips:仅仅设置元素的值并不是结构的修改
                     我们知道的是ArrayList是线程不安全的,如果在使用迭代器的过程中有其他的线程修改了List就会
                  抛出ConcurrentModificationException这就是Fail-Fast机制。   
                     那么快速失败究竟是个什么意思呢?
                 在ArrayList类创建迭代器之后,除非通过迭代器自身remove或add对列表结构进行修改,否则在其他
                 线程中以任何形式对列表进行修改,迭代器马上会抛出异常,快速失败。    

           8、迭代器遍历

                    我们知道Collection接口都有产生迭代器的接口,作为List接口的具体实现类ArrayList来说肯定是

               少不了的,而且ArrayList同时还提供一个ListIterator实现更为灵活的遍历,这里我们就Iterator做一些

               探究吧。看看产生Iterator的方法 

/*返回一个迭代器接口的具体实现类*/  public Iterator<E> iterator() {         return new Itr();  }      /**      * An optimized version of AbstractList.Itr      */     private class Itr implements Iterator<E> {         int cursor;       // index of next element to return         int lastRet = -1; // index of last element returned; -1 if no such         int expectedModCount = modCount;          public boolean hasNext() {             return cursor != size;         }          @SuppressWarnings("unchecked")         public E next() {             checkForComodification();             int i = cursor;             if (i >= size)                 throw new NoSuchElementException();             Object[] elementData = ArrayList.this.elementData;             if (i >= elementData.length)                 throw new ConcurrentModificationException();             cursor = i + 1;             return (E) elementData[lastRet = i];         }          public void remove() {             if (lastRet < 0)                 throw new IllegalStateException();             checkForComodification();              try {                 ArrayList.this.remove(lastRet);                 cursor = lastRet;                 lastRet = -1;                 expectedModCount = modCount;             } catch (IndexOutOfBoundsException ex) {                 throw new ConcurrentModificationException();             }         }          final void checkForComodification() {             if (modCount != expectedModCount)                 throw new ConcurrentModificationException();         }     }

                 通过阅读源码,很容易就知道了,通过迭代器我们并不需要知道集合的底层实现,我们只需


           生成一个迭代器对象,通过迭代器对象提供的方法,实现集合的遍历等操作。

                留意到的是checkForComodification可以知道,当多个线程同时操作一个ArrayList造成

           modeCount不一致时,会抛出ConcurrentModificationException异常,仔细观察又可以发现的是

           迭代器每一个方法执行都需要调用checkForComodification()方法,这样就实现了上面所说的

           Fail-Fast(快速失败),相较于迭代完成之后抛出异常更合理些。 

            9、一个小"陷阱"

                  通过阅读源码笔者还发现了一个有意思的问题,那就是subList(index1,index2)方法,返回一个

             从index1(包括)开始到index2(不包括)的子列表。通过观察可发现的是这里子列表的幕后还是原

             列表,对其的修改也会造成原列表的修改。

                如果不想出现这种情况,很简单 new ArrayList(list.subList(index1,index2));就ok了。

                好了,对于List、ArrayList的整理就到这个地方了,对于一些细节的东西笔者难免有些遗漏的 

            地方,希望给予指出与交流!