Java集合答疑解惑之ArrayList:无参构造的数组为空?

174 阅读3分钟

ArrayList :动态数组

ArrayList是Java开发者最熟悉的集合之一,我们必须熟悉它的实现细节。同时ArrayList也非常适合Java初学者作为迈出开始阅读源码的第一步。但:

  • 为什么无参构造方法中初始化数组为空数组?
  • DEFAULT_CAPACITY = 10是如何生效的?

看完这篇文章,相信你会知道这些问题的答案。

核心属性

// 大小
private int size;
// 数组
transient Object[] elementData;

// 默认容量空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认容量
private static final int DEFAULT_CAPACITY = 10;

看到了一个关键字DEFAULT_CAPACITY = 10,那么是否可以大胆地猜测ArrayList无参构造会 new 一个大小为10的elementData数组呢?但是为什么又有那么多空数组呢?别急,慢慢往下看

构造方法

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

你会发现,无参构造仅仅是把一个空数组赋值给elementData数组字段。说好的DEFAULT_CAPACITY呢? 但转念一想,在你使用ArrayList的时候,是不是直接add?那我们来看看add方法。

add:插入元素

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

可以看到在ensureCapacityInternal(size + 1); 之后就可以直接操作数组了,那么问题的核心一定在ensureCapacityInternal(size + 1);之中。

ensureCapacityInternal:确保数组的容量足够

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

可以看到,DEFAULT_CAPACITY终于出场了,也就是在无参构造方法的第一次put时,会执行ensureExplicitCapacity(DEFAULT_CAPACITY)方法

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

elementData是个空数组,显然条件成立,因此可以看到,原来第一次put时就会触发扩容方法grow。不要惊讶,ArrayList的grow方法并不只是简单地复制数组。

ArratList扩容机制:grow

来看grow方法

private void grow(int minCapacity) {
// 去掉了一些无关紧要的部分
    // 旧容量
    int oldCapacity = elementData.length;
    // 新容量 >> 1 相当于 / 2 可以看到是1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 关键就在这
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 底层是本地方法 System 的数组拷贝 
    elementData = Arrays.copyOf(elementData, newCapacity);
}

当新容量newCapacity小于minCapacity时,newCapacity = minCapacity;还记得minCapacity传入的是多少吗?没错,就是DEFAULT_CAPACITY = 10。终于,elementData被初始化完毕,且数组的长度为10。

其实,如果你爱看英文注解,你会发现在elementData上方的注解就有写明这一点

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
t

虽然知道了DEFAULT_CAPACITY = 10是如何奏效的,但是还有个问题,为什么ArrayList源码要这样写呢?

个人愚见:这是一种懒加载思想,倘若你new了许多个ArrayList但都没有使用,但它们内部都有一个长度为10的数组,这会造成空间资源的浪费。等到插入元素时再初始化,就是很经典的懒加载思想的应用。