public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
-
继承了 AbstractList ,实现了 List,提供了相关的增删改查功能。
-
实现了 RandomAccess 接口,提供了随机访问的能力。
-
实现了 Cloneable 接口,就是覆盖了 clone() 方法,使得 ArrayList 实例能被克隆。
-
实现了 java.io.Serializable 接口,支持序列化。
ArrayList 主要的属性
================
//基础容量
private static final int DEFAULT_CAPACITY = 10;
//动态数组
transient Object[] elementData;
//空数组,供参数为集合或整数的构造器使用(详细请看下面的分析)
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组,供空参构造器使用(详细请看下面构造器的分析)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//动态数组的长度(集合中元素的个数)
private int size;
//记录数组被修改的次数,用于防止fail-fast(具体看下面的分析)
protected transient int modCount = 0;
//动态数组的最大容纳量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
光看属性似乎会有很多疑问或不解,大概对这些属性有个印象,结合下面的方法来看吧。
ArrayList 的构造器
==============
在 ArrayList 中,提供了三个构造器:空参构造器、参数为集合的构造器,参数为整数的构造器。
1. 空参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2. 参数为集合的构造器
public ArrayList(Collection<? extends E> c) {
//将集合转化为数组
elementData = c.toArray();
//判断c.toArray()的长度是否为0
if ((size = elementData.length) != 0) {
// 检查c.toArray()是否为Object类型的数组,如果不是,则重新拷贝成一个Object类型的数组
if (elementData.getClass() != Object[].class)
//使用了Arrays这个工具类来进行浅复制,关于Arrays工具类的实现请移步相关文章
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果c.toArray()的长度为0,那么就直接使用预定义的空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
3. 参数为整数的构造器
public ArrayList(int initialCapacity) {//将合法的整型参数作为动态数组的初始化容量
//如果初始化容量大于0,则将参数作为数组的初始化容量
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//如果参数等于0,则使用预定义的空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {//处理不合法的参数(参数小于0),抛出异常
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
常用的方法
=====
一、添加方法
在 ArrayList
中,提供了以下 4 个用于增加元素的方法:
1. boolean add(E e)
—— 该方法用于向集合的末尾添加一个元素,返回一个布尔值,表示操作是否成功。
public boolean add(E e) {
//检查容量是否满足,具体分析看下面
ensureCapacityInternal(size + 1);
//将元素添加到数组的末尾
elementData[size++] = e;
//所有错误都在相应的方法里面处理了,所以此处返回true
return true;
}
private void ensureCapacityInternal(int minCapacity) {//参数为最小容量
//判断动态数组是否为预定义的空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果为空数组,则取出 默认容纳量(10)和最小容量两者之中的最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {//参数为最小容量(就是上面方法那两者的最大值)
//修改计数器自增
modCount++;
//如果最小容量比动态数组的长度大,则进行扩容
if (minCapacity - elementData.length > 0)
//真正扩容的方法
grow(minCapacity);
}
private void grow(int minCapacity) {
//动态数组没添加元素之前元素的个数
int oldCapacity = elementData.length;
//新容量是没添加元素之前元素个数的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量小于最小需要的容纳量,则将最小需要的容纳量作为新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量大于预定义的动态数组最大容纳量(详细看属性那里),则将最大容纳量作为新容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//通过Arrays工具实现扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//处理不合法参数
if (minCapacity < 0)
throw new OutOfMemoryError();
//如果需要的最小容纳量大于属性MAX_ARRAY_SIZE,则返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE
//注:MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 (详细看属性的定义)
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
2. void add(int,E)
—— 添加元素到指定的位置。
public void add(int index, E element) {
//处理不合法的参数index。其实就是检测数组的下标是否越界(详细的分析看后面)
rangeCheckForAdd(index);
//检测数组容量是否满足
ensureCapacityInternal(size + 1);
//这是一个native方法,死活不让我看源码!
//大致可以看出意思就是将index位置后面的所有元素往后移一位,空出index这个位置
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//在数组的index这个位置存储元素的值,从而就实现了插入元素的效果
elementData[index] = element;
//size是集合中元素的个数,自增
size++;
}
private void rangeCheckForAdd(int index) {
//如果索引大于数组的长度或者小于0,则为不合法,抛出异常。
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3. boolean addAll(Collection<? extend E>)
—— 用于合并两个集合,意思就是将两个集合的元素放在源集合中,返回布尔值,表示操作是否成功
public boolean addAll(Collection<? extends E> c) {
//老套路,先将集合转换为数组
Object[] a = c.toArray();
//转换后数组的长度
int numNew = a.length;
//还是熟悉的配方,这是检测容量的那套方法,前面已经分析过了。
ensureCapacityInternal(size + numNew);
//该死的native方法
//大致的意思就是将数组的所有元素拷贝到集合的末尾
System.arraycopy(a, 0, elementData, size, numNew);
//更新长度
size += numNew;
return numNew != 0;
}
4. boolean addAll(int,Collection<? extend E>)
—— 将一个集合中所有元素插入到源集合中的 index
位置,结果返回布尔值,表示操作是否成功。
public boolean addAll(int index, Collection<? extends E> c) {
//检测参数是否合法。下标是否越界,在前面已经分析过了。
rangeCheckForAdd(index);
//一样的套路:转化为数组
Object[] a = c.toArray();
int numNew = a.length;
//考虑容量
ensureCapacityInternal(size + numNew); // Increments modCount
//本地代码无法查看,大致意思就是将index位置后面所有元素往后移,空出a.length个位置,然后将数组的所有元素插入到其中。
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
二、修改与获取元素的方法
在 ArrayList
中,修改与获取元素的方法分别只有一个,就是 get
和 set
。
E set(int index, E element)
—— 修改 index
位置的元素,结果返回被修改的元素。
public E set(int index, E element) {
//这个方法与前面判断越界的方法有所不同,具体看下面的分析
rangeCheck(index);
//获取旧的值
E oldValue = elementData(index);
//设置新的值
elementData[index] = element;
return oldValue;
}
private void rangeCheck(int index) {
//显然,不同之处就是少了一个条件:index < 0,这也就是意味着可以传递一个负数作为参数
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E get(int index)
—— 获取 index
位置的元素。
public E get(int index) {
//检查下标是否越界
rangeCheck(index);
//返回数组index位置的元素
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
三、删除元素的方法
在 ArrayList
中,提供了四个用于删除元素的方法。
1. E remove(int index)
—— 移除集合中某个位置的元素,结果返回这个元素。
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;
}
2. boolean remove(Object o)
—— 删除某个元素,如果集合中存在多个,则删除首次出现的,结果返回布尔值,表示是否成功。
public boolean remove(Object o) {
//分开两种情况考虑,一种为元素为null,另一种为非null,但是做法都是一致,遍历数组,拿到元素对应的索引,然后删除。
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) {//该方法用于删除某个位置的元素(其实逻辑跟前面那个remove方法是一致的!)
//修改计数器
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
}
3. boolean removeAll(Collection<? extend E> c)
—— 在源集合中移除参数集合 c
所有的元素,意思就是求差集,返回布尔值表示是否成功
public boolean removeAll(Collection<?> c) {
//检查参数是否为null
Objects.requireNonNull©;
//批量删除
return batchRemove(c, false);
}
public static T requireNonNull(T obj) {
//如果参数为null,则抛出异常,否则返回本身
if (obj == null)
throw new NullPointerException();
return obj;
}
private boolean batchRemove(Collection<?> c, boolean complement) {
//获取动态数组
final Object[] elementData = this.elementData;
//r为循环计数器;
int r = 0, w = 0;
//修改标识
boolean modified = false;
try {
//遍历动态数组
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
4. void clear()
—— 清空集合。
public void clear() {
//修改计数器
modCount++;
//将所有元素置为null
for (int i = 0; i < size; i++)
elementData[i] = null;
//将长度置为0
size = 0;
}
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ar() {
//修改计数器
modCount++;
//将所有元素置为null
for (int i = 0; i < size; i++)
elementData[i] = null;
//将长度置为0
size = 0;
}
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-7EguScdK-1715779362423)]
[外链图片转存中…(img-pOMMUps2-1715779362424)]
[外链图片转存中…(img-6wOGSvC8-1715779362424)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!