Java集合List系列(一):ArrayList源码解析(JDK1.8)

本文详细解析了Java集合List中的ArrayList类,包括其属性、构造方法、元素新增、删除、查找以及转换成数组等核心方法。ArrayList基于数组实现,支持动态扩容,其构造方法有指定容量、无参和传入集合三种。在添加元素时,会检查并扩容数组,首次扩容为原容量的1.5倍。删除元素涉及元素的移动,可能影响性能。转换成数组通常使用toArray()或toArray(T[] a)方法,其中toArray(T[] a)能返回指定类型的数组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

图1-1 全文思维导图

概述

ArrayList是基于数组实现的,并且支持 动态扩容的动态数组。相比于数组而言,因为其支持 自动扩容的特性,成为我们在开发中最常用的集合类之一。

本篇的讲解是基于 JDK1.8,话不多说, 就让我们翻开 ArrayList的源代码,遨游一番吧~

Let's GO !!!

类图

图 1-2 ArrayList类图

从类图中我们可以看出,ArrayList实现了4个接口和继承了1个抽象类;4个接口分别为:

  • List接口,主要提供数组的添加、删除、修改、迭代遍历等操作。

  • Cloneable接口,表示ArrayList支持克隆。

  • RandomAccess接口,表示ArrayList支持 快速的随机访问。

  • Serializable接口,表示ArrayList支持序列化的功能。

继承的抽象类为 AbstractList,主要提供的是 迭代遍历相关的操作;如果我们单纯看 List --> AbstractList --> ArrayList这一实现/继承关系来看的,是不是有一种 模板方法模式的感觉呢? List接口定义了操作行为,AbstractList抽象类提供了可重用的 算法骨架;而最终的 ArrayList实现类根据自己的情况自定义实现接口。

注意:实际上 ArrayList大量重写了 AbstractList抽象类提供的方法实现;所以 AbstractListArrayList来说意义不大,但不过 AbstractList的其他很多子类享受到了这个福利。

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

源码解析

在进行源码解析前,我们先全览一篇 ArrayList中的所有方法吧,并且挑选比较核心的方法进行讲解。

/****************** ArrayList中的构造函数 ***************/
// 默认构造函数
ArrayList()

// capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
ArrayList(int capacity)

// 创建一个包含collection的ArrayList
ArrayList(Collection<? extends E> collection)

/****************** ArrayList中的API ********************/
// Collection中定义的API
boolean             add(E object)
boolean             addAll(Collection<? extends E> collection)
void                clear()
boolean             contains(Object object)
boolean             containsAll(Collection<?> collection)
boolean             equals(Object object)
int                 hashCode()
boolean             isEmpty()
Iterator<E>         iterator()
boolean             remove(Object object)
boolean             removeAll(Collection<?> collection)
boolean             retainAll(Collection<?> collection)
int                 size()
<T> T[]             toArray(T[] array)
Object[]            toArray()

// AbstractCollection中定义的API
void                add(int location, E object)
boolean             addAll(int location, Collection<? extends E> collection)
E                   get(int location)
int                 indexOf(Object object)
int                 lastIndexOf(Object object)
ListIterator<E>     listIterator(int location)
ListIterator<E>     listIterator()
E                   remove(int location)
E                   set(int location, E object)
List<E>             subList(int start, int end)

// ArrayList新增的API
Object               clone()
void                 ensureCapacity(int minimumCapacity)
void                 trimToSize()
void                 removeRange(int fromIndex, int toIndex)

属性

ArrayList的属性很少,只有2个属性: elementDatasize;其中 elementData代表的是存放元素的数组;而 size代表的是 elementData数组中元素的数量。比如下图所示: elementData数组的长度是8,但是只存放了4个元素,这时候 size属性就是4。

我们在使用 ArrayList的时候会经常调用其 size()方法,返回的就是 elementData数组中 已使用 的元素的数量,也即是当前的 4;并且当我们新添加元素的时候,size所指代的也恰巧是新元素的下标(数组下标从0开始)。

图 1-3 ArrayList属性

/**
  * 存放元素的数组
  * 在新增元素的时候,如果遇到数组不够会创建新的数组,并且将原数组的元素拷贝到新数据;最后将该应用变量指向新数组
  */
transient Object[] elementData;

/**
  * 数组的大小
  * size代表的是已使用elementData的元素的数量,也即是size()方法的大小
  */
private int size;

构造方法

ArrayList一共有3个构造方法,分别如下所示。

1. ArrayList(int initialCapacity)

ArrayList(int initialCapacity)构造方法,根据传入的 initialCapacity初始化容量来创建 elementData数组。如果我们在使用 ArrayList的时候,预先知道元素的数量,应该尽可能的使用该构造方法,这样可避免数据扩容,从而提升性能,同时也是合理的利用内存空间(一点不浪费~)。

public ArrayList(int initialCapacity) {
   
    if (initialCapacity > 0) {
   
        // 初始化容量大于0,创建Object数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
   
        // 初始化容量为 0 时,使用 EMPTY_ELEMENTDATA 对象
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
   
        // 参数异常
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}

这里需要留意的是:当初始化容量 initialCapcity为 0 的时候,使用了一个 EMPTY_ELEMENTDATA对象,查看源码可知其定义: private static final Object[] EMPTY_ELEMENTDATA = {}是一个空数组。在添加元素的时候,会进行扩容创建需要的数组。

2. ArryaList()

无参构造方法,平时使用的时候,如果你的IDEA有安装 sonarLint扫描,会被提示:创建集合的时候要指定初始化容量噢~

public ArrayList() {
   
    // 实际创建的时候是空数组,在首次添加元素的时候,才会初始化容量为10的数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

无参构造方法使用了 DEFAULTCAPACITY_EMPTY_ELEMENTDATA对象,查看源码可知其定义:private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}是一个空数组。

似乎有朋友会有疑问:“我之前学习的时候被灌输的是:当未指定初始化大小的时候,ArrayList默认的大小是10呀!”

这句话一定程度上来说是没问题的,只是缺失了一些补充描述:当未指定初始化大小的时候,ArrayList先是初始化为一个空数组;但在首次添加元素时,ArrayList才会初始化为一个容量为10的数组。这么做的原因是节省内存,一些场景可能只是单纯的定义了数组并没有真实的使用,直接初始化容量为10的数组会造成不必要的浪费。

细心的朋友可能会有疑问:“既然都是空的数组,那为什么不直接使用 EMPTY_ELEMENTDATA空数组;而是要重新定义一个新的空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA呢?”

别急,正所谓:“存在即是合理。” 设计者这么做肯定有其特殊的考虑,后面在介绍 数组扩容的时候会详细介绍两者的不同;DEFAULTCAPACITY_EMPTY_ELEMENTDATA的首次扩容是 10, EMPTY_ELEMENTDATA的首次扩容是按照1.5倍扩容,从0开始;两者的起点不同。

3. ArrayList(Collection<? extends E> c)

ArrayList(Collection<? extends E> c)构造方法,使用传入的集合 c 作为 ArrayList的 elementData

public ArrayList(Collection<? extends E> c) {
   
    // element指向c.toArray()  toArray()方法会创建一个新的数组
    elementData = c.toArray
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值