概况

ArrayList 是我们常用的一种数据结构。仔细分析 ArrayList 这个类和类所包含的方法。

签名

ArrayList继承了AbstractList 和使用了List RandomAccess Cloneable和Serializable四个接口。

AbstractList该接口的作用是用于 AbstractList 提供了 List 接口的默认实现,在AbstractList中已经使用了List接口,为什么还会在ArrayList中使用的List接口,目测是为了让大家更加明白清楚的知道这个类是List这个集合。 RandomAccess

变量

ArrayList只有两个私有变量,分别是sizeelementDataelementData这个是来记录传入ArrayList的元素。而size是用来记录传入的元素的个数。

构造器

该类里面一共有三个构造器

  • ArrayList()
  • ArrayList(int)
  • ArrayList(Collection)

下面就详细的分析一下三个构造器的使用。

ArrayList()

ArrayList无参的构造器,使用这个无参构造器会默认有一个长度为10的数组。

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

ArrayList(int)

传入一个int值,这个值为ArrayList的初始容量。如果等于0则会默认使用EMPTY_ELEMENTDATA生成一个空的集合。如果输入为非负数的话会抛出一个IllegalArgumentException(非法参数)异常。

     public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

ArrayList(Collection)

这个构造函数是传入一个集合,toArray是将这个集合转化为一个实际的数组,下面的程序就是和传入int类型是差不多的,判断数组长度是否为0,如果等于0则会默认使用EMPTY_ELEMENTDATA生成一个空数组。在不为空的时候,将Collection的值copy到ArrayList

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

方法

trimToSize()

该方法主要是用于将ArrayList实际容量调整为列表当前大小。这里它实用了一个三目运算? :

  • modCount这个变量在AbstractList这个类里面,其中定义int这个变量使用了transient

protected transient int modCount = 0;

transient 当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中。

  • 三目运算和if else的区别,三目运算是会有一个返回值的,if else没有返回值。如果非要写成if else应该也可以。
 public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

ensureCapacity()

如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。 DEFAULT_CAPACITY=10是一个常量

public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }

indexOf()

返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1。更确切地讲,返回满足 (o==null ? get(i)==null : o.equals(get(i))) 的最低索引 i ,如果不存在此类索引,则返回 -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;
    }

contains()

如果此列表中包含指定的元素,则返回 true。更确切地讲,当且仅当此列表包含至少一个满足 (o==null ? e==null : o.equals(e)) 的元素 e 时,则返回 true。

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

lastIndexOf()

lastIndexOf用于返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。 这里ifelse里面采用了两种不同的比较方法。if代码块里面是采用了==说明比较的是对象,而else的代码块采用的是equals比较。为什么这里采用两种比较方法呢?

这里产生一个疑问?

null是对象还是值?

正在技术论坛上提问


Keywords > 50 character sequences, formed from ASCII letters, are reserved for use as keywords and cannot be used as identifiers (§3.8). Keyword: one of

 abstract  continue   for          new         switch
 assert     default    if           package     synchronized
 boolean    do         goto         private     this
 break      double     implements   protected   throw
 byte       else       import       public      throws
 case       enum       instanceof   return      transient
 catch      extends    int          short       try
 char       final      interface    static      void
 class      finally    long         strictfp    volatile
 const      float      native       super       while

The keywords const and goto are reserved, even though they are not currently used. This may allow a Java compiler to produce better error messages if these C++ keywords incorrectly appear in programs. While true and false might appear to be keywords, they are technically Boolean literals (§3.10.3). Similarly, while null might appear to be a keyword, it is technically the null literal (§3.10.7).


在这个50个关键字中没有null,但是下面的一行字写了。

While true and false might appear to be keywords, they are technically Boolean literals (§3.10.3). Similarly, while null might appear to be a keyword, it is technically the null literal (§3.10.7).

虽然truefalse可能看起来是关键字,但它们在技术上是布尔值(§3.10.3)。类似地,虽然null可能看起来是一个关键字,但在技术上是null(§3.10.7)。

java的官方文档里写着true,false,null是一个值,也就是说java有50个关键字,3个特殊的值。 竟然是值,为什么在比较的时候会报错?

public class Main {
    public static void main(String[] args) {
        String a = null;
        String b = null;
        System.out.println(a == b);
        System.out.println(a.equals(b));
    }
}

输出结果

        true
    Exception in thread "main" java.lang.NullPointerException
    	at Main.Main.main(Main.java:14)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
    
    Process finished with exit code 1

第二个方法抛出一个空指针异常。 竟然是一个值,那么应该两种都会显示ture

这样也就说明了为什么和null比较时要用==而不是equals

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;
        }
       

clone()

返回此 ArrayList 实例的浅表复制。(不复制这些元素本身。)

什么是浅表复制(shallow copy) 对于shallow copy的翻译很多,有叫浅表复制,浅复制,影子复制,与它向对的是深度复制。 浅表复制 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象

   /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
 public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

add(E)

该方法是用于添加将指定元素添加到末尾,通过ensureCapacityInternal()这个方法来为ArrayList扩容,在为elementData的末尾添加指定元素。代码的注释也强调了,是增加modCount

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

add(int,E)

这个方法的一开始就调用了rangCheckForAdd()这个方法,这个私有方法主要是用来判断传入的索引,如果传入的数值大于最大值或者小于零会抛出一个越界异常。System.arraycopy()是用来复制数组的,将要添加位置之后的数组整体后移一位。

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

remove(Object)

移除此列表中首次出现的指定元素(如果存在)。如果列表不包含此元素,则列表不做改动。更确切地讲,移除满足 (o==null ? get(i)==null : o.equals(get(i))) 的最低索引的元素(如果存在此类元素)。如果列表中包含指定的元素,则返回 true(或者等同于这种情况:如果列表由于调用而发生更改,则返回 true)。

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

clear()

通过一个循环将ArrayList中的元素赋值为null,最后将size赋值为0,这样应该就会保证没有浪费内存。

 public void clear() {
        modCount++;

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

        size = 0;
    }

ListIterator()

用于集合遍历,如果索引大于最大值或者小于0,抛出异常,否者使用ListItr()进行。

public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

ListItr()

这是一个私有的内部类,继承了Iterator这个接口,

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();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

replaceAll()

这个方法是用来替换所有的匹配项

传入的参数是UnaryOperator是在java 8引入的lambda表达式,具体如何使用以后再说。

   public void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            elementData[i] = operator.apply((E) elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

sort()

 public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }