用这篇ArrayList讲解来完爆面试官!!
文章目录
前言
作为一个95后的新时代码农,一个在互联网公司生存的工具人,一个专科毕业的码农,每次面试都被面试官面的完无体肤,于是在一个寂寞难耐的夜晚,我痛定思痛,希望能帮助各位读者。
前言只是我的一个嘘头,我们应该有一颗谦逊的心(我现在是真的很菜),所以希望大家怀着心态好好学,一起进步!
类图
- 实现了 RandomAccess 接口,可以随机访问。
- 实现了 Cloneable 接口,可以进行克隆。
- 实现了 Serializable 接口,可以进行序列化和反序列化。
- 实现了 List 接口,是 List实现类之一。
- 实现了 Iterable 接口,可以使用for-each进行循环迭代。
属性
// 序列化版本UID
private static final long serialVersionUID = 8683452581122892189L;
/**默认的初始容量*/
private static final int
DEFAULT_CAPACITY = 10;
/** 用于空实例的共享空数组实例 new ArrayList(0);*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**用于提供默认大小的实例的共享空数组实例*/
private static final Object[]
DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储ArrayList元素的数组缓冲区
* ArrayList的容量,是数组的长度
*/
transient Object[] elementData;
/**ArrayList中元素的数量*/
private int size;
- 为什么空实例默认数组有的时候是
EMPTY_ELEMENTDATA
,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 为什么
elementData
要被transient
修饰 - 为什么
elementData
没有被private
修饰?
让我们带着问题,继续往下看!
构造方法
带初始容量的构造方法
/**
* 带一个初始容量参数的构造方法
*
* @param initialCapacity 初始容量
* @throws 如果初始容量非法就抛出
* IllegalArgumentException
*/
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);
}
}
- 如果容量大于0.则 elementData 等于一个Object长度的数组。
- 如果容量等于0,则使用 EMPTY_ELEMENTDATA 。
- 其他情况(容量小于0的情况下),会抛出异常。
无参构造方法
/**
* 无参构造方法 将elementData 赋值为
* DEFAULTCAPACITY_EMPTY_ELEMENTDATA
*/
public ArrayList() {
this.elementData =
DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
带一个集合参数的构造方法
/**
* 带一个集合参数的构造方法
*
* @param c 集合,代表集合中的元素会被放到list中
* @throws 如果集合为空,抛出NullPointerException
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
// 如果 size != 0
if ((size = elementData.length) != 0) {
// c.toArray 可能不正确的,不返回 Object[]
// https://bugs.openjdk.java.net/browse/JDK-6260652
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(
elementData, size, Object[].class);
} else {
// size == 0
// 将EMPTY_ELEMENTDATA 赋值给 elementData
this.elementData = EMPTY_ELEMENTDATA;
}
}
- 使用将集合转换为数组的方法。
- 为了防止
c.toArray()
方法不正确的执行,导致没有返回Object[]
,特殊做了处理 - 如果数组大小等于
0
,则使用EMPTY_ELEMENTDATA
什么情况下c.toArray()
会不返回Object[]
呢?
那么,让我们来写一个测试demo看看:
在代码的最后一行,会爆出 存储错误错误,为什么呢?我们来debug来看一下。
我们可以看出 asList的toarray没有返回Object[],而list的toarray(),返回了Object[],那么,这究竟是为什么呢????
稳住,别慌!! 我们看ArrayList的toArray()
方法源码:
我们看到使用了Arrays.copyOf()
方法:
我们debug追到了这里,我们看到了 调用的cppyOf方法:
我们看到 使用了三目运算来判断类型,如果newType是Object[] copy 数组 类型就是 Object 否则就是 newType 类型。ArrayList中elementData
就是Object[]
类型,所以ArrayList的toArray()
方法必然会返回Object[]
。
某同学心里提问:“在demo中,为什么asList返回的不是Object呢??”
我们可以看到,我们是使用Arrays.asList的方法:
这时候,某同学又提问了!!!这不就是带参数的ArrayList吗??? 错!!!
我们这里的a是一个泛型,而我们使用的构造器是 Arrays内部的ArrayList:
所以呢,我们看到java.util.Arrays
的内部ArrayList的toArray()
方法,是构造方法接收什么类型的数组,就返回什么类型的数组。所以,我们在我们的例子中,赋值为 Obejct自然会抛异常!!
插入方法
add(E e) 在列表的最后添加指定元素
/**
* 在列表最后添加指定元素
*
* @param e 要添加的指定元素
* @return true
*/
public boolean add(E e) {
// 增加 modCount !!
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
add(int index, E element) 在指定位置添加指定元素
/**
* 在指定位置添加指定元素
* 如果指定位置已经有元素,就将该元素和随后的元素移动到右面一位
*
* @param index 待插入元素的下标
* @param element 待插入的元素
* @throws 可能抛出 IndexOutOfBoundsException
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
// 增加 modCount !!
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
在父类AbstractList
上,定义了modCount
属性,用于记录数组修改的次数。
**欸??又有同学提问了!!!!这个 ensureCapacityInternal
是干嘛的,直接插入到最后不就可以了吗??? **
判断是否为之后,我们可以看到 代码会从 默认容量和 minCapactiy中选出一个最小的容量当作elementData 数组长度。
而在 ensureExplicitCapacity
方法中:
就是在这里记录的 modCount,然后我们看到了最小容量 的校验,调用了grow方法:
- 通常情况新容量是原来容量的1.5倍
- 如果原容量的1.5倍比
minCapacity
小,那么就扩容到minCapacity
- 特殊情况扩容到
Integer.MAX_VALUE
看到这里我们要不要想一下我们的第一个问题?
为什么空实例默认数组有的时候是EMPTY_ELEMENTDATA
,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
原来,new ArrayList()
会将elementData
赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
new ArrayList(0)
会将elementData
赋值为 EMPTY_ELEMENTDATA,EMPTY_ELEMENTDATA,
添加元素会扩容到容量为1
,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10
。
我们看到,我们使用有参构造0的时候,ensureCapacityInternal()的执行:
我们可以看到 minCapacity为1 ,因为 ementData在有参构造的时候,赋值为:EMPTY_ELEMENTDATA
,而 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
并不等于 EMPTY_ELEMENTDATA
我们再来看扩容的方法,为容量最小为1的时候:
这里我们注意一下,因为 我们刚初始化(使用有参构造),所以elementData里面是没有元素的,而0右移一位,还是0;
删除元素方法
remove(Object o) 移除指定元素方法
/**
* 移除第一个在列表中出现的指定元素
* 如果存在,移除返回true
* 否则,返回false
*
* @param o 指定元素
*/
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;
}
remove(int index) 删除指定下标元素
/**
* 移除列表中指定下标位置的元素
* 将所有的后续元素,向左移动
*
* @param 要移除的指定下标
* @return 返回被移除的元素
* @throws 下标越界会抛出IndexOutOfBoundsException
*/
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);
// 将引用置空,让GC回收
elementData[--size] = null;
return oldValue;
}
以为为List 最常用的 添加和删除,我们来总结一下:
- ArrayList底层的数据结构是数组
- ArrayList可以自动扩容,不传初始容量或者初始容量是
0
,都会初始化一个空数组,但是如果添加元素,会自动进行扩容,所以,创建ArrayList的时候,给初始容量是必要的 Arrays.asList()
方法返回的是的Arrays
内部的ArrayList,用的时候需要注意
文章中没有提到点,或者你有什么其他好奇的地方,欢迎留言讨论。我们再见…