JDK 源码分析:ArrayList
JDK 源码分析:ArrayList
System
tags
Java
数组
源码分析
date
May 29, 2021
attribute
笔记
source

1. 继承关系

notion image
  • 继承自 AbstractList 抽象类:AbstractList 抽象类中规定了一些公共方法、迭代器方法、modCount
  • 实现了 List 接口:List 接口中规定了一些公共行为方法。
  • 实现了 Serializable 接口:标识为可序列化,需要定义一个不重复的 serialVersionUID 成员常量。
  • 实现了 Cloneable 接口:标识为可克隆,必须实现 clone() 方法。
  • 实现了 RandomAccess 接口:标识为支持随机访问,给遍历方式提供了选型依据。

2. 核心属性

2.1 默认初始容量

/**
 * 默认初始容量
 *
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;
ArrayList 的默认容量,初始值为 10 的常量,只在公开的 ensureCapacity() 方法和私有的 ensureCapacityInternal() 方法中被使用。

2.2 空数组

/**
 * 空数组
 *
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};
一个被初始化为 {} 的空数组常量,虽然里面没有元素,长度为 0,但是已经分配了数组的内存首地址。

2.3 用于默认初始容量的空数组

/**
 * 用于默认初始容量的空数组
 *
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

2.4 元素数组

/**
 * 实际存储元素的数组,使用 transient 关键字避免序列化,为了内部类的访问所有是非私有的。
 *
 * 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.
 */
transient Object[] elementData; // non-private to simplify nested class access
ArrayList 中的元素就存在这个 elementDataObject[] 数组中。

2.5 长度

/**
 * 元素的个数,size() 方法返回的就是这个属性值。
 *
 * The size of the ArrayList (the number of elements it contains).
 *
 * @serial
 */
private int size;
用于记录 ArrayList 里已添加元素的个数。

3. 核心概念

3.1 元素个数和容量

ArrayList 中:
  • 元素个数:指的是 elementData 的实际元素个数,size() 方法返回的就是这个。
  • 容量:指的是 elementData.length 的长度。
这两个是不同的概念,一般情况下,为了保证数组边界安全, elementData.length 往往都会比 elementData 的实际元素个数要大,对于 elementData 的扩容也是针对 elementData.length 的扩容。

3.2 空数组常量和默认初始容量的空数组常量

既然已经有了 EMPTY_ELEMENTDATA,为什么还需要定义这个同样初始化为空数组的常量?
属性 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 常量十分类似于 EMPTY_ELEMENTDATA,但它们的作用各有各的不同,在官方注释中有写道:我们将其(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)与 EMPTY_ELEMENTDATA 区别开来,以便在添加第一个元素时可以判断应该扩容多少。
上文也提到常量属性 EMPTY_ELEMENTDATA 已经分配了内存地址,对于 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 来说也如此,虽然他们都是初始化了一个空数组 {},但他们初始化后的内存地址并不同,进行确保容量逻辑时就是根据它们不同的内存地址来进行分支走向。

3.3 结构修改次数

在继承的 AbstractList 抽象类中定义了被 protectedtransient 修饰的 int 类型变量 modCount
/**
 * 此变量用于记录 List 的结构被修改的次数,用于在迭代器中实现快速失败机制,
 * 这个字段的使用对于子类来说是可选的,如果子类希望实现快速失败机制的话就要
 * 在添加元素、删除元素等会导致 List 结构改变的方法中自增此值。
 *
 * The number of times this list has been <i>structurally modified</i>.
 * 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.
 *
 * <p>This field is used by the iterator and list iterator implementation
 * returned by the {@code iterator} and {@code listIterator} methods.
 * If the value of this field changes unexpectedly, the iterator (or list
 * iterator) will throw a {@code ConcurrentModificationException} in
 * response to the {@code next}, {@code remove}, {@code previous},
 * {@code set} or {@code add} operations.  This provides
 * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
 * the face of concurrent modification during iteration.
 *
 * <p><b>Use of this field by subclasses is optional.</b> If a subclass
 * wishes to provide fail-fast iterators (and list iterators), then it
 * merely has to increment this field in its {@code add(int, E)} and
 * {@code remove(int)} methods (and any other methods that it overrides
 * that result in structural modifications to the list).  A single call to
 * {@code add(int, E)} or {@code remove(int)} must add no more than
 * one to this field, or the iterators (and list iterators) will throw
 * bogus {@code ConcurrentModificationExceptions}.  If an implementation
 * does not wish to provide fail-fast iterators, this field may be
 * ignored.
 */
protected transient int modCount = 0;
modCount 变量被用于快速失败(fail-fast)机制,快速失败机制是 Java 集合中的一种机制,在用迭代器变量一个集合对象时,通过判断 modCount 的值是否一致,就可得知在遍历过程中集合对象的内容结构是否发生了变化(添加元素、删除元素、修改元素等),如果确实发生了变化就会立马抛出 ConcurrentModificationException 异常,防止调用者在不注意的情况下并发修改集合对象导致一些难以排查的错误。
java.util 包下的集合类都是支持快速失败的,这也就以为则它们都不支持多线程环境下的并发修改(遍历过程中修改),换而言之它们都不是线程安全的。
java.util.concurrent 包下的集合类都是采用安全失败(fail-safe)机制的,它们可以在多线程环境下并发修改,换而言之它们都是线程安全的。

4. 提出问题

  1. EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA 的具体区别和作用是什么?分别在什么情况下被使用?
  1. 为什么 elementData 要用 transient 修饰?
  1. 为什么 elementData 没有被 private 修饰?
  1. modCountArrayList 中的具体体现在什么地方?
  1. ArrayList 的扩容机制具体是什么逻辑?

5. 构造方法

5.1 空参构造

/**
 * 空参构造,使用默认初始容量空数组常量来初始化元素数组,注意此构造
 * 和初始容量构造同样是初始化为空数组,但是它们的地址不一样,后续会
 * 在添加元素时通过判断这个地址来选择扩容数量。
 *
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
空参构造会使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 常量的空数组来初始化 elementData,如此一来它们的地址都是相同的:elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
此时还没有真正分配 ArrayList 的容量。

5.2 初始容量构造

/**
 * 通过指定初始容量构造。
 *
 * 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) {
    // 如果指定的初始容量参数大于 0
    if (initialCapacity > 0) {
        // 则 new 出同等长度的数组
        this.elementData = new Object[initialCapacity];
    // 如果指定的初始容量参数等于 0
    } else if (initialCapacity == 0) {
        // 则使用空数组常量来初始化元素数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 小于 0 则抛异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
如果指定的初始容量参数为 0,会使用 EMPTY_ELEMENTDATA 来初始化 elementData

5.3 集合类构造

/**
 * 通过集合类构造,会将集合类转为数组,并按照顺序拷贝到元素数组中。
 *
 * 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<? extends E> c) {
    // 将集合转成数组来初始化元素数组
    elementData = c.toArray();
    // 进一步判断初始化后的元素数组是否为空,此时为 size 赋了值。
    if ((size = elementData.length) != 0) {
        // toArray() 有 BUG(编号 6260652),不一定就会返回 Object[],所以要进一步判断
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            /**
             * 如果被初始化后的元素数组不是 Object[] 类型,则通
             * 过 Arrays 方法 new 出新的 Object[] 来再次赋值。
             */
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        // 如果元素数组为空则和指定了 0 容量的初始容量构造器一样,用空数组初始化。
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
ArrayList 提供了使用 Collection 的子类来初始化自己的构造方法,提高集合间的互操作性。

6. 核心方法

6.1 添加元素

/**
 * 添加新元素到数组末尾
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    /**
     * 插入前先要保证元素数组的容量是否足够,因为会将新元素赋值到 size 处
     * 后 size 要 + 1,所以传入的最小所需容量是 size + 1,会依据此参数进
     * 行容量确保逻辑。如果容量足够则会继续向下执行,如果容量不足会先进行数组
     * 扩容逻辑再继续向下执行。
     */
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    /**
     * 将要添加的新元素赋值到 size 处后,随即 size + 1,保证 size 所代表
     * 的 ArrayList 的元素个数始终是正确的、最新的。
     */
    elementData[size++] = e;
    return true;
}
添加元素前会先进行确保容量逻辑,保证不会因为容量不足而导致数组越界溢出。
确保容量逻辑需要传入最小所需容量 minCapacity 参数,添加元素时最小所需容量必然是当前的数组元素个数 size + 1

6.2 扩容机制

/**
 * 确保容量逻辑
 *
 * @param minCapacity 最小所需容量
 */
private void ensureCapacityInternal(int minCapacity) {
    // 判断元素数组地址得知是不是通过默认初始容量的空数组初始化的
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        /**
         * 由此元素数组地址可知当前 ArrayList 是通过空参构造进行初始化的,且默认
         * 初始容量 DEFAULT_CAPACITY 常量为 10,之后在首次调用 add() 方法时数
         * 组容量会从 0 扩容到 10。如果是通过指定容量参数构造的就不会走这一步,
         * 会扩容到指定容量 + 1 的长度,对于 EMPTY_ELEMENTDATA 数组会扩容到
         * 长度 1(因为 size 为 0,add() 方法结束后才会对 size 修改,
         * minCapacity 参数的值是 ensureCapacityInternal(size + 1))。
         */
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
ensureCapacityInternal() 方法
此处是 JDK 7 的代码,与 JDK 8 的代码格式略有不同,但核心代码和逻辑基本一致。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
JDK 8
开始处理确保容量逻辑,此方法主要的主要作用是处理传入的 minCapacity 参数,根据 elementData 是否是通过 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 进行初始化的情况来判断逻辑走向:
  • 通过默认初始容量空数组初始化的情况:如果传入的 minCapacity 参数小于预设的默认初始容量常量 DEFAULT_CAPACITY 的值 10,就使用 DEFAULT_CAPACITY 来作为 minCapacity,否则继续沿用传入 的 minCapacity 的值。
  • 反之:既然不是通过默认初始容量空数组来初始化的,没有默认容量要求,那么就继续沿用 minCapacity 的值。
处理完 minCapacity 后继续传入确保实际容量方法。
/**
 * 确保实际容量
 *
 * @param minCapacity 最小所需容量,由上层调用的方法逻辑得知,通过
 *        DEFAULTCAPACITY_EMPTY_ELEMENTDATA 来初始化 elementData
 *        的话,首次扩容 minCapacity 值为 10;通过 EMPTY_ELEMENTDATA 来初始化
 *        话,首次扩容 elementData 时 minCapacity 值为 1,其他情况 minCapacity
 *        的值为 size + 1。
 */
private void ensureExplicitCapacity(int minCapacity) {
    // 结构修改次数 + 1
    modCount++;

    // overflow-conscious code
    // 确保 minCapacity 大于 elementData.length 来避免溢出,
    if (minCapacity - elementData.length > 0)
        // 如果最新所需容量确实比 elementData 的长度要大,则会进行数组扩容
        grow(minCapacity);
}
ensureExplicitCapacity() 方法
💡
modCountArrayList 中的 add()remove()writeObject() 等方法中都有调用。
该方法的作用主要是判断是否需要对元素数组进行扩容,确定扩容前就已经对 modCount 变量进行自增,表明接下来会发生结构变化的情况,如果这时其他线程在遍历当前集合对象,根据 modCount 的值不同,会直接抛出 ConcurrentModificationException 异常使其快速失败。
在 JDK 11 时移除了 ensureCapacityInternal()ensureExplicitCapacity() 方法。
/**
 * 扩容方法,通过 minCapacity 来判断扩容逻辑。
 *
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    // 存下旧容量
    int oldCapacity = elementData.length;
    // 新容量等于 旧容量 + (旧容量 / 2),实则是扩容了 1.5 倍
    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:
    /**
     * 使用新容量来扩容,通过 Arrays.copyOf(T[] original, int newLength)
     * 方法来建立以 newCapacity 为长度的新数组,并将 elementData 元素依次拷贝
     * 过去,elementData 就得到了新的长度,保留了之前的元素,旧的 elementData
     * 数组内存空间随着没有引用指向,GC Roots 不可达后被 JVM 进一步回收。
     */
    elementData = Arrays.copyOf(elementData, newCapacity);
}
grow() 方法
实际进行元素数组扩容的方法,处理了三种扩容的情况:
  • 元素数组的新容量会被扩容到旧容量的 1.5 倍。
  • 如果计算出来的新容量小于最小所需容量,那就直接采用最小所需容量来进行扩容。
  • 如果计算出来的新容量大于最大支持的数组容量,则会继续进入巨大容量的确保逻辑。
扩容时使用的是 Arrays.copyOf(T[] original, int newLength) 方法,会返回一个拥有新长度的新数组的副本,旧数组中的元素也会按照原顺序依次拷贝到新数组中。
💡
这就是 ArrayList 的扩容机制。

6.3 确保巨大容量逻辑

/**
 * 最大支持容量常量,最高支持到 Integer 的最大值,但考虑到一些虚拟机会在数组前
 * 存一些 header words,所以预留 8 个空间长度,防止在一些虚拟机上溢出。
 *
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList 中提前定义好了最大支持的数组容量常量。
/**
 * 确保巨大容量逻辑,根据 minCapacity 来判断允许多大的容量。
 *
 * @param minCapacity 最小所需容量
 *
 * @return 返回最终允许的容量
 */
private static int hugeCapacity(int minCapacity) {
    // minCapacity 小于 0 就直接异抛出 OOM
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    /**
     * 如果申请的最小所需容量比最大支持容量要大,就给它最大的 Integer,
     * 后果由调用者自己承担。如果申请的最小所需容量没有超过最大支持容量,
     * 但扩容后的新容量确实超过了,就给他最大支持的容量,在调用者可接受
     * 的范围内尽量保证数组内存安全。
     */
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
确保巨大容量逻辑时根据传入的最小所需容量参数来决定的:
  • 最小所需容量大于最大支持的数组容量:直接返回 Integer 的最大值,对于 JVM 的兼容性和安全性由调用者自己考虑。
  • 最小所需容量小于最大支持的数组容量:返回定义好的、兼容性高的最大支持数组的容量。

6.4 显式要求扩容

/**
 * public 的显示确保容量逻辑。
 *
 * Increases the capacity of this <tt>ArrayList</tt> instance, if
 * necessary, to ensure that it can hold at least the number of elements
 * specified by the minimum capacity argument.
 *
 * @param   minCapacity   the desired minimum capacity
 */
public void ensureCapacity(int minCapacity) {
    /**
     * 如果 elementData 是通过 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 来
     * 初始化的,且其还没有被扩容过,就使用 DEFAULT_CAPACITY 来作为
     * minExpand,否则 minExpand 为 0。
     */
    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;

    // 确保最小指定容量要比 minExpand 大,否则无意义
    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}
可以在 add() 大量元素之前调用该 ensureCapacity() 方法,以减少增量重新分配的次数。
💡
EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATAensureCapacityInternal()ensureCapacity() 方法中有着重要的作用,通过不同的地址可以得知元素数组的初始化方式,从而进行不同的处理。

6.5 容量收缩

/**
 * 将 ArrayList 的容量收缩至数组元素长度,减少内存占用。
 *
 * Trims the capacity of this <tt>ArrayList</tt> instance to be the
 * list's current size.  An application can use this operation to minimize
 * the storage of an <tt>ArrayList</tt> instance.
 */
public void trimToSize() {
    modCount++;
    // 判断数组元素个数是否小于数组实际长度
    if (size < elementData.length) {
        // elementData 的收缩逻辑判断
        elementData = (size == 0)
          // 数组元素个数本来就是 0 的话会被收缩为 EMPTY_ELEMENTDATA
          ? EMPTY_ELEMENTDATA
          // 创建一个刚好存放数组元素个数的新数组,并将元素依次复制进去
          : Arrays.copyOf(elementData, size);
    }
}
可以显式要求 ArrayList 进行容量收缩,如果数组元素个数为 0 的话,就会收缩为 EMPTY_ELEMENTDATA,否则 elementData 会收缩到与元素个数 size 一样的长度 length

6.6 克隆对象

/**
 * 返回这个 ArrayList 实例的浅拷贝。需要注意的是该 clone() 方法与通
 * 过集合类构造方法、addAll() 方法一样都是只进行浅拷贝操作,拷贝的
 * 只是数组元素的引用,如果对被拷贝的数组元素进行修改,那么也会影响
 * 到相同拷贝的数组元素值。
 *
 * 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 {
        // 调用 Object类中默认的 clone() 方法来浅拷贝
        ArrayList<?> v = (ArrayList<?>) super.clone();
        // 手动将元素数组中实际有效元素引用拷贝到克隆对象中
        v.elementData = Arrays.copyOf(elementData, size);
        // 将克隆对象的结构修改次数重置回 0
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}
需要注意的是该 clone() 方法与通过集合类构造方法、addAll() 方法一样都是只进行浅拷贝操作,拷贝的只是数组元素的引用,如果对被拷贝的数组元素进行修改,那么也会影响到相同拷贝的数组元素值。
平时最多存储的一般是 Integer、String 这些不可变的元素类型,所以尽管是浅拷贝也不会影响到原来的元素值,对于其他那些并非不可变的元素类型就需要注意了,可以使用序列化和反序列化来对 ArrayList 实例进行深拷贝。

6.7 序列化

/**
 * 实现的序列化方法
 *
 * Save the state of the <tt>ArrayList</tt> instance to a stream (that
 * is, serialize it).
 *
 * @serialData The length of the array backing the <tt>ArrayList</tt>
 *             instance is emitted (int), followed by all of its elements
 *             (each an <tt>Object</tt>) in the proper order.
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    // 开始序列化前记录 modCount 的值
    int expectedModCount = modCount;
    // 将当前类的非 static 和非 transient 字段全部序列化到流中
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    // 将元素数组中有效的元素个数序列化至流中
    s.writeInt(size);

    // Write out all elements in the proper order.
    /**
     * 紧随其后的是该 ArrayList 实例的所有元素,手动将 elementData 中
     * 实际有效的元素依次序列化至流中,忽略那些无效元素,提高效率节省内存。
     */
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    // 序列化后会判断 modCount 的值是否发生了变化,来发生快速失败
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
序列化方法会判断 modCount 的值来决定是否发生快速失败。
💡
elementDatatransient 关键字修饰就是为了要手动序列化,只序列化有效的元素,节省内存提高效率。

7. 小结

  • ArrayList 底层的数据结构是数组。
  • ArrayList 可以自动扩容,不用初始容量构造或者初始容量是 0 的话,都会初始化一个空数组,但是两种初始化方式得出的数组地址是不一样的。如果这时添加元素,会自动进行扩容,所以,创建 ArrayList 的时候,给初始容量是有必要的。
  • Arrays.asList() 方法返回的是的 Arrays 内部的 ArrayList,需要注意。
  • subList() 返回的是内部类,不能序列化,和 ArrayList 共用同一个元素数组。
  • 迭代删除要用迭代器的 remove() 方法或者倒序的for循环。
  • ArrayList 重写了序列化、反序列化方法,避免序列化、反序列化全部数组,节省空间提高效率。
  • elementData 不使用 private 修饰,可以简化内部类的访问,就算使用 private 修饰,编译器也会优化语法,通过内联静态的 access() 方法来直接访问。