Java集合详解-ArrayList

概述

ArrayList继承自AbstractList,实现了List接口,本质上是一个默认初始长度为10,且支持自动扩容的数组。ArrayList非线程安全,多线程环境下操作需要开发人员保证线程安全

关键变量和常量

    // 数组初始容量
    private static final int DEFAULT_CAPACITY = 10; 

    // 数组容器,用于存放ArrayList元素. 
    transient Object[] elementData; 

初始化

ArrayList提供三种初始化方法

  • 不传参
    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

使用该方式创建ArrayList时,elementData初始化为空数组,使用DEFAULT_CAPACITY = 10 作为初始容量

  • 传入初始容量
    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

初始化时传入初始容量,并将elementData初始化为对应长度的数组,传入0时和第一种初始化方法相同,传入小于0的容量时,抛出IllegalArgumentException异常

  • 传入集合
    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection 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;
        }
    }

该方法很好理解,就是将传入的集合转换成数组,并赋值给elementData, 关键在于if (elementData.getClass() != Object[].class)这行代码,加这个判断的意图是什么?注释的含义是c.toArray()返回的不一定是Object[]类型,这里需要了解一下JDK-6260652 Bug

举个例子,使用如下代码创建一个ArrayList

        List l1 = Arrays.asList(new String[]{"123"});
        Object[] objs = l1.toArray();
        System.out.println(objs.getClass()); // 此处输出class [Ljava.lang.String; 
 

Arrays.asList(new String[]{"123"}).toArray().getClass()返回的是String[]类型,而非Object[]类型,如果再执行objs[0] = new Object();,会抛出java.lang.ArrayStoreException异常。

具体原因要看Arrays#ArrayList.toArray()方法

        public  T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }

如上代码所示,Arrays#ArrayList.toArray()返回的是T[]类型,如果传入String数组,则返回的数组类型是String[]类型。对于一个String[]类型数组,如果赋一个Object的值,显然会出错。

因此才会有上面那个判断,判断是否为Object[]类型,如果不是,将数组转换为Object[]类型,避免后续赋值时出现java.lang.ArrayStoreException异常

获取

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

ArrayList根据下标获取元素时,首先判断index是否越界,如果越界,抛出IndexOutOfBoundsException异常,否则返回对应下标的元素

添加

添加元素到ArrayList有两个方法

  • 添加元素到指定位置
    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++;
    }
    
    // 检查index是否越界
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    // 计算数组所需的容量,如果elementData为空数组,则从DEFAULT_CAPACITY, minCapacity取较大值返回
    // 否则返回传入的minCapacity
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    // 判断数组是否需要扩容,如需要,调用grow方法扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }   
    
    // 扩容数组。
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 右移一位,即原有基础上扩容50%
        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);
    }

首先检查index是否越界,越界抛出IndexOutOfBoundsException异常,然后判断数组是否需要扩容,如需扩容,扩容后将元素添加到指定index,具体请看代码注释

  • 添加元素到末尾
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

主要流程和上面方法类似,不贴了

删除

  • 删除指定位置的元素
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

常规操作,第一步先判断是否越界。此处modCount变量用于统计ArrayList发生结构变化的次数,如果modCount的值与预期不相符,会抛出ConcurrentModificationException异常,一般出现在遍历ArrayList时,删除或添加元素的场景。
然后获取对应index的值,计算要移动的元素数量,如果numMoved > 0,则通过System.arraycopy,将index + 1之后的numMoved个元素拷贝到index位置;如果numMoved == 0,直接赋值为null,等待GC回收

  • 删除第一个查找到的对象
    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;
    }
    
    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; // clear to let GC do its work
    }    

该方法传入一个对象,遍历找到ArrayList中对应的第一个对象后,将其删除,流程和上面一个方法差不多

替换

替换指定位置的元素

    public E set(int index, E element) {
        rangeCheck(index);

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

首先判断index是否越界,越界抛出IndexOutOfBoundsException异常,未越界则从elementData中获取对应index的值,并将对应index的值设置为新值,然后返回oldValue

modCount

用于记录数组结构变更次数,每一次添加或删除元素时,都会+1.在使用迭代器遍历时,如果modCount和预期不一致,会抛出ConcurrentModificationException异常

你可能感兴趣的:(java,集合,arraylist)