ArrayList集合
一. 介绍
ArrayList是List接口的一个实现类,它是Java程序中最常用的集合之一。在ArrayList内部,它使用一个可变长度的数组来存储元素。当向ArrayList中添加元素时,如果当前的数组容量不足以容纳新增的元素,ArrayList会自动进行扩容操作,创建一个更大的数组,并将原始数据复制到新数组中。这样就实现了ArrayList的长度可变性。通过索引可以快速访问和修改ArrayList中的元素,同时也支持进行插入、删除等操作。ArrayList允许null值存在。
ArrayList是有序的数组,当你向ArrayList中添加元素时,它们会按照添加的顺序进行存储,并且保持这个顺序。因此,你可以通过索引来访问ArrayList中的元素,并且它们会按照添加的顺序进行返回。
ArrayList结构图:
ArrayList继承AbstractList<E>,AbstractList是一个抽象类,提供了一些通用的列表操作方法。
ArrayList实现RandomAccess接口,表示允许通过索引直接访问集合中的元素;
ArrayList实现Cloneable接口,表示可以进行克隆操作;
ArrayList实现Serializable接口,表示可以进行序列化操作。
二. ArrayList的优点
动态增长:ArrayList底层数据结构是Object数组(Object[])。在ArrayList中,每次添加、删除、查询等操作都是直接对这个底层的Object数组进行实现。当元素数量超过当前数组长度时,ArrayList会根据需要自动进行扩容操作,通常会创建一个新的更大的数组,并将原有元素复制到新数组中。这样就能保证ArrayList的高效操作和动态调整大小的特性。
高效的随机访问:ArrayList内部以数组形式存储元素,元素是按照索引顺序存储的,每个元素占据连续的内存空间。由于数组具有随机访问的特性,通过索引可以直接计算出元素在内存中的地址,从而实现快速的随机访问操作。因此支持通过索引快速随机访问元素。可以使用get()方法通过索引获取元素,时间复杂度为O(1)。
方便的插入和删除操作:ArrayList提供了丰富的方法来进行元素的插入和删除操作。通过add()方法可以在任意位置插入元素,通过remove()方法可以删除指定位置的元素,或者通过元素值来删除。
支持多种数据类型:ArrayList可以存储任何类型的对象,包括基本数据类型的包装类、自定义对象等。这使得ArrayList成为一个很灵活的集合类,适用于各种场景。
支持迭代操作:ArrayList实现了Iterable接口,因此可以使用增强的for循环或者迭代器来遍历集合中的元素。这使得对ArrayList进行遍历操作变得非常便捷。
三. 源码分析
成员方法:
private static final int DEFAULT_CAPACITY = 10;
默认初始化容器 = 10;
privatestaticfinal Object[] EMPTY_ELEMENTDATA = {};
用于空实例的共享空数组 ,一般在构造方法指定空容器,或在elementData、size()为0的时候使用。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
共享的空数组实例用于默认大小的空实例。我们通过区分它与EMPTY_ELEMENTDATA来知道在添加第一个元素时需要扩容多少。
ransient Object[] elementData;
ArrayList的内部使用数组缓冲区来存储其元素。
ArrayList的容量是该数组缓冲区的长度。当第一个元素被添加到elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList时,它将会被扩展到DEFAULT_CAPACITY(默认容量)。
private int size;
ArrayList的大小(包含的元素数)
构造方法:
1. 构造一个具有指定初始容量的空列表。
@param initialCapacity 列表的初始容量
@throws IllegalArgumentException 如果指定的初始容量为负,则声明非法数据异常
public ArrayList(int initialCapacity) { // 该构造方法指定初始容量
if (initialCapacity > 0) { // 如果初始容量>0 则 elementData.size() = 初始容量
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { // 如果初始容量=0 则elementData.size() = 0 (EMPTY_ELEMENTDATA )
this.elementData = EMPTY_ELEMENTDATA;
} else { // 如果初始化容量<0 则抛出异常:非法数据异常
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
// 该构造方法指定初始化容量,只是为ArrayList分配足够的内存空间来存储元素,避免在添加大量元素时频繁进行扩容操作,指定初始化容量有助提高性能和效率,初始化大小并不表示列表中已经有了元素。
2. 构造一个初始容量为10的空列表。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 该构造方法不指定初始容量,而是在add()添加的时候,会判断是否使用的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是,则返回 DEFAULT_CAPACITY 的值。
// 也就是说,该构造方法,默认的初始容量为0,在调用add() 方法后size() 会扩张为10;
3. 构造一个包含指定集合元素的列表,按照集合迭代器返回的顺序排列。
@param c 要放入此列表中的元素所属的集合
@throws NullPointerException 如果指定的集合为null。
public ArrayList(Collection<? extends E> c) { // 该类的构造方法指定一个 Collection<E> 集合.
Object[] a = c.toArray(); // 返回 c 集合中所有元素的数组,使用变量a接收;
if ((size = a.length) != 0) { // 将a的长度赋值给 size,并判断是否不等于0,如果为true,则执行下面
if (c.getClass() == ArrayList.class) { // 判断类是否相同;
elementData = a; // 相同则将传参的地址引用赋值给 elementData ;
} else { // 不同则使用 Arrays.copy() 工具类将 a 的元素拷贝到新的 Object[] 中,并赋值给 elementData ;
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array. // 如果 size() 的长度为0,则转为空数据,EMPTY_ELEMENTDATA
elementData = EMPTY_ELEMENTDATA;
}
}
// 该构造方法指定传入Collection集合,并获取集合内的元素引用地址。如果传入Collection.size() == 0,则转为空数据,如果Collection.size() != 0,则赋值元素的引用地址。
常用方法
1. add(E e) 将指定的元素追加到此列表的末尾。
@param e 要追加到该列表的元素
@return <tt>true</tt>(与{@link Collection#add}指定的一样)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! // 扩容机制判断
elementData[size++] = e;
return true;
}
// 该方法用于ArrayList,添加新元素。
// ensureCapacityInternal(size + 1); 该方法用于判断及执行扩容。
// elementData[size++] = e ; 数组形式赋值新元素。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 该方法内部含私有方法,用于判断容量临界点及扩容。
// alculateCapacity(Object[] elementData, int minCapacity) 该方法用于动态标记当前最小容量。
// ensureExplicitCapacity(int minCapacity) 该方法用于判断是否达到最小容器临界点,是否需要扩容。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 该方法用于动态标记最小容量,第一次add(),会将容量标记为10,第二次add(),会将容量标记为2 ... 依次类推,标记容量。
// 参数elementData表示当前ArrayList实例的存储数组
// 参数minCapacity表示的最小容量。
// 如果elementData为默认的空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),即ArrayList实例没有初始化过,则返回默认容量DEFAULT_CAPACITY和minCapacity中较大的那个值作为容量;否则,直接返回minCapacity作为容量。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) //
grow(minCapacity);
}
// 该方法判断是否到达临界点,如果是则扩容
// modCount:继承自AbstractList的成员变量, 作用是修改计数,在每次add(E e) 或者 remove() 时候 modCount++ ,是为了实现"fail-fast"机制而存在的;
// 参数 minCapacity 表示ArrayList需要的最小容量
// 参数 elementData.length表示当前数组的容量
// 条件判断minCapacity - elementData.length > 0,即表示需要的最小容量大于当前数组容量,才需要进行扩容操作。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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:
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 该方法用于扩容给ArrayList扩容。
// (oldCapacity >> 1 是一个位运算符,表示将oldCapacity右移一位,即将二进制表示的oldCapacity向右移动一位,相当于将oldCapacity除以2取整数部分(舍弃小数部分)。
简单来说,oldCapacity >> 1就是将旧容量除以2,舍弃小数部分,得到的新容量。 int newCapacity = oldCapacity + (oldCapaciyt/2);即容量扩大为原来的1.5倍。
// 如果新容量比最小容量还要小,那么将 newCapacity 设为 minCapacity,以确保容量不会小于所需最小值。
// MAX_ARRAY_SIZE : MAX_ARRAY_SIZE 可以被认为是一个上限值,表示数组的最大容量,其值为:Integer.MAX_VALUE - 8;。
// 如果 newCapacity 超过了这个上限,就调用 hugeCapacity(minCapacity) 方法来计算一个更大的容量,并将 newCapacity 设为计算得到的值。
// 调用Arrays.copyOf工具类,其内部调用的是System.arraycopy( ),用户将源数组拷贝到新数组中。
// 其扩容方式也就是说new一个新的Object[] ,指定其数组容量,并拷贝其中所有内容。因此每次在扩容期间,会有损性能。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
// 方法用于计算和返回一个足够大的容量值,以满足所需的最小容量
// 如果 最小容量 < 0 ,则抛出 "请求的数组大小超出了虚拟机限制" 异常;
// 三元运算符,最小容量是否大于MAX_ARRAY_SIZE,如果大于则返回 Integer.MAX_VALUE,如果小于则返回 MAX_ARRAY_SIZE;
总结:add(E e) 将指定的元素追加到此列表的末尾,此方法在执行后会调用 ensureCapacityInternal()方法,用来动态标记当前执行对象的最小容量,若elementData达到最小容量(minCapacity),会在通过grow()方法,扩容1.5倍。扩容的操作是使用Arrays.copyOf工具类拷贝到一个new Object[] 数组中。因此每次扩容期间,会有损性能,每次扩容都会将原数组元素拷贝到新的数组中。
2. add(int index, E element) 在列表的指定位置插入指定的元素。将当前位置上的元素(如果有)以及后续的元素都向右移动(索引加一)。
@param index 要插入指定元素的索引位置
@param element 要插入的元素
@throws IndexOutOfBoundsException {@inheritDoc} 抛出索引越界异常
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
// rangeCheckForAdd(index); 该方法主要用于检查索引范围是否有效。
// ensureCapacityInternal(size + 1); 此方法与add(E e)中重复,故不在详情描述。该方法是用于判断容量临界点及扩容。
// System.arraycopy() 用于数组拷贝的方法。它允许将一个源数组的特定范围的元素复制到目标数组的特定位置。
// elementData[index] = element; 指定数组的索引位置,添加元素
// size++; 元素数量+1;
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 该方法主要用于检查索引范围是否有效。
// 如果指定插入元素的位置大于当前元素的长度或者小于0,则会抛出索引越界异常。
3. addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到当前集合中。
* 将指定集合中的所有元素按照其迭代器返回的顺序追加到此列表的末尾。如果在操作正在进行时修改了指定集合,则此操作的行为是未定义的(这意味着如果指定的集合是此列表且此列表非空,则此调用的行为是未定义的)。
@param c 包含要添加到此列表的元素的集合
@return 如果此列表由于调用而发生更改,则返回<tt>true</tt>
@throws NullPointerException 如果指定的集合为空
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray(); // 转换为Object[]
int numNew = a.length; // 获取数组的长度
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew; // 计算集合的元素长度
return numNew != 0; // 如果传入的集合为空元素,则返回fasle,如果有元素,则返回true
}
// ensureCapacityInternal(size + 1); 此方法与add(E e)中重复,故不在详情描述。该方法是用于判断容量临界点及扩容。
// System.arraycopy() 用于数组拷贝的方法。它允许将一个源数组的特定范围的元素复制到目标数组的特定位置。
4. indexOf(Object o) 查找指定元素在列表中第一次出现的索引位置。
// ArrayList是允许null值的,所以在逻辑判断中,需要判断是否为null;
// 此处先判断 传参是否 == null ,如果为null的情况下,则查询数组中该元素的索引位置,若有则返回该索引的位置,若无则返回 -1;如果传参不为null, 则进入else中 使用 equals() 方法获取对象值,若有则返回该索引的位置,若无则返回 -1;
// 此处使用 if(o==null) {} 做比较,是因为 null 值不能使用equals() 作比较。
5. lastIndexOf(Object o) 方法用于返回指定元素在列表中最后一次出现的索引位置,如果列表不包含该元素,则返回-1。
// 此方法是使用发 forr 倒序索引查询元素是否存在,方法与上方 indexOf(Object o) 基本相同。
6. contains(Object o) 用于判断是否包含指定的元素
* 如果此列表包含指定的元素,则返回true。更正式地说,当且仅当此列表至少包含一个元素e满足(o==null ? e==null : o.equals(e))时,返回true。
@param o 要测试其在此列表中是否存在的元素
@return 如果此列表包含指定的元素,则返回true
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
// 该方法调用了indexOf(Object o) ,如果使用indexOf() 查询有内容,返回true,如果没有返回false;
7. ensureCapacity(int minCapacity) 指定最小容量,即指定elementData的长度。
* 如果需要的话,增加此 ArrayList 实例的容量,以确保它至少可以容纳指定的最小容量参数指定的元素数量。
@param minCapacity 期望的最小容量
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
// 首先判断elementData 是不是空数据,是空数据,则 minExpand = 0 ,不是空数据,则minExpand = 10;
// 在比较 minCapacity 是否大于 minExpand ,如果大于,则执行 ensureExplicitCapacity 进行判断是否需要扩容。
// ensureCapacityInternal(size + 1); 此方法与add(E e)中重复,故不在详情描述。该方法是用于判断容量临界点及扩容。
// 此方法与构造方法 ArrayList(int initialCapacity) 意思相同,指定其最小容量的大小。
8. trimToSize() 用于将 ArrayList 实例的容量调整为列表的当前大小
· public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
}
}
// modCount++:这行代码用于增加 ArrayList 的修改计数器,以便在并发操作时能够检测到结构上的修改;
// if (size < elementData.length):此条件判断当前元素的数量是否小于内部数组的长度,即判断是否存在多余的空间。如果存在多余的空间,则进行下一步;
// (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size):这行代码通过三元运算符判断当前列表的大小是否为 0。如果大小为 0,则将 elementData 设置为空数组(EMPTY_ELEMENTDATA)。否则,使用 Arrays.copyOf() 方法将 elementData 数组复制到一个新的数组中,并将新数组的大小设置为当前元素的数量 size。
9. get(int index) 方法用于返回列表中指定位置的元素。
@param index - 要返回的元素的索引
@return - 列表中指定位置的元素
@throws IndexOutOfBoundsException {@inheritDoc} - 如果索引超出了列表的范围
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
// 判断 index 是否超出元素长度。
// 返回该索引所在位置的元素,注意此处是直接使用 Object[],索引应该是从 0 开始。
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 该方法用于检查索引是否超出元素长度。
// 如果超出长度,则抛出索引越界异常。
10. set(int index, E element) 用指定的元素替换列表中指定位置的元素。
@param index - 要替换的元素的索引。
@param element - 要存储在指定位置的元素。
@return - 先前位于指定位置的元素。
@throws IndexOutOfBoundsException {@inheritDoc} - 如果索引超出了列表的范围。
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
// rangeCheck(index); 该方法用于检查索引是否超出元素长度。
// elementData(index); 查询该索引所在位置的元素,并赋值给 oldValue ,E 为实例
// elementData[index] = element; 替换index所在位置的元素,
// 返回已被替换的值,注意是 oldValue,是已经被替换掉的值,所以如果需要获取替换的值,可以使用get() 方法,或遍历。
11. toArray() 方法用于将 ArrayList 对象转换为数组,返回Object[]
@return 包含此列表中所有元素的数组,按照正确的顺序(从第一个元素到最后一个元素)
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
// 此方法使用Arrays.copyOf 方法,创建一个新的数组,并将原始数组的元素复制到新数组中,返回Object[] 。
12. toArray(T[] a) toArray()方法的重载,可指定目标数组的类型、设置新元素的默认值等
* 返回一个包含列表中所有元素按正确顺序排列的数组;返回的数组的运行时类型与指定数组的运行时类型相同。如果列表适合指定的数组,则将列表复制到该数组中并返回。否则,将使用指定数组的运行时类型和列表的大小分配一个新数组。
如果指定的数组可以容纳列表并有多余空间(即数组的长度大于列表的长度),则在集合末尾的下一个位置设置为 null。(仅当调用者知道列表不包含任何 null 元素时,此功能对确定列表长度很有用)
@param:要将 a 列表元素存储到其中的数组,如果足够大,则直接使用;否则会为此目的分配一个相同运行时类型的新数组。
@return:包含列表元素的数组。
@throws ArrayStoreException:如果指定数组的运行时类型不是列表中每个元素的超类型。
@throws NullPointerException:如果指定的数组为 null。
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
// 在 Java 的泛型(Generic)中,编译器会进行类型检查以确保代码的类型安全性,而使用 @SuppressWarnings("unchecked") 可以过滤带有泛型参数,不受检查方法时导致的警告。
// 传参为T[] a ,代表可传入任意类型数组对象,且返回值<T>T[] 代表返回该类型的指定类数组。
// 如果传入的数组 a 大小不足以容纳列表元素,则创建一个新的数组,并将列表的元素拷贝到新数组中,并返回新数组。这里使用了 Arrays.copyOf() 方法来创建新的数组,该方法会根据指定类型和长度复制原数组的内容。
// 如果传入的数组 a 大小足够容纳列表元素,则通过 System.arraycopy() 方法将列表中的元素复制到传入的数组 a 中。该方法可以实现高效的数组拷贝。
// 如果传入的数组 a 大于列表的大小,则将传入数组的第 size 个位置设置为 null,以标记后续位置为空。
// 需要注意的是,代码中的 (T[]) 是强制类型转换,用于将 Object[] 类型的数组转换为泛型类型 T[]。在类型擦除的情况下,这种类型转换可能引发 ClassCastException 异常。
13. remove(int index) 根据索引删除对应元素
@param index 要删除的元素的索引
@return 从列表中被删除的元素
@throws IndexOutOfBoundsException {@inheritDoc}:如果索引越界,则抛出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);
elementData[--size] = null;
}
// rangeCheck(index); 方法同上,用于检查索引是否超出元素长度。
// 获取elementData中索引位置的元素,并赋值给oldValue,numMoved 获取当前索引位置开始到末尾还有多少个元素 ;如果numMoved大于0,表示在索引之后还有元素,此时通过System.arraycopy()方法将索引之后的所有元素向前移动一位,填补掉被删除的元素的位置。之后,将最后一个元素置为null,并且将List的大小减1;如果numMoved小于0,则直接赋值为空数据。
// 返回值为被删除的元素 oldValue 。
14. remove(Object o) 把指定元素从列表中移除,仅移除第一次出现的元素。
* 把指定元素从列表中移除,仅移除第一次出现的元素。如果列表不包含该元素,列表保持不变。
@param o 要从列表中移除的元素(如果存在)
@return 如果列表包含指定的元素,则返回true
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;
}
// 首先判断传参是否为null,若为null,则通过该for循环查询该值所在的位置,并通过fastRemove(index) 进行删除元素,减去size长度,返回 true 。若不为null,则通过equals() 方法判断值所在的位置,在通过调用fastRemove(index) 方法,返回 true 。若传参不在elementData数组中,将返回 false。
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
}
// 该方法在remove(int index) 中出现过,作用是在给定的索引位置从ArrayList中快速移除元素。当调用该方法时,它会删除指定索引处的元素,并且通过将后续元素向前移动来填补被删除元素的空缺,以保持ArrayList中其它元素的连续性。
15. subList(int fromIndex, int toIndex) 方法返回一个指定范围的子列表。
* 返回一个列表的视图,该视图包含从指定的fromIndex(包括)开始到toIndex(不包括)结束的部分。(如果fromIndex和toIndex相等,则返回的列表为空。)返回的列表是原列表的支持,因此返回列表中的非结构性更改会反映在原列表中,反之亦然。返回的列表支持所有可选的列表操作。# 从该子表做出修改,会将原表中的数据修改,反之亦然。
* 这个方法消除了需要显式进行范围操作的需求(通常适用于数组)。任何期望接收一个列表的操作都可以通过传递一个子列表视图来作为范围操作。例如,下面的惯用法可以从列表中移除一段元素: list.subList(from, to).clear(); 类似的表达方式可以用于 indexOf(Object) 和 astIndexOf(Object),并且可以将 Collections 类中的所有算法应用于子列表。该代码片段的含义是删除列表中指定范围的元素。使用subList(from, to)方法获取指定范围的子列表,并调用clear()方法清空子列表中的元素,从而实现删除操作。
* 此方法返回的列表的语义将变得未定义,如果除了通过返回的列表之外,该列表(即此列表)以任何方式进行<i>结构修改</i>。(结构修改是指改变此列表的大小或以其他方式干扰它,可能导致进行中的迭代产生不正确的结果。)# "未定义"是指在特定情况下,结果无法确定或不具备明确的含义。在这个语境中,如果对返回的列表进行结构修改(改变大小或干扰列表),结果将变得未定义,即无法预测或保证那些被修改的操作会产生怎样的效果或影响。这可能导致迭代过程产生不正确的结果,因为修改列表的结构可能使迭代过程发生错误。因此,建议在对返回的列表进行任何结构修改之前,仔细考虑可能的影响和副作用。
@throws IndexOutOfBoundsException(数组越界异常):当您访问数组或集合的索引超出其有效范围时,会抛出该异常。
@throws IllegalArgumentException(非法参数异常):当您传递给方法的参数不符合方法要求或无效时,会抛出该异常。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
// 该方法返回一个指定范围的子列表,它是通过AbstractList类的内部类SubList实现的。
// subListRangeCheck(fromIndex, toIndex, size); 用于检查索引范围是否有效。
// fromIndex 表示开始索引位置(包括),toIndex 表示结束索引位置(不包括) ;举例:从0开始索引,到第4个元素 ,fromIndex = 0 ,toIndex = 5,包头不包尾。
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
}
// 方法用于检查subList()方法中指定的子列表的索引范围是否有效,以避免出现索引越界的情况。。
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset; int size;
SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
// 该类是属于ArrayList的私有内部类,使用内部类的主要原因有以下几点:
// 封装性:通过将子列表作为ArrayList的内部类,可以将子列表的具体实现细节隐藏在ArrayList类内部,对外部用户而言,只需知道如何使用子列表即可,无需了解其具体实现。
// 内部类访问权限:内部类可以访问外部类的私有成员,这意味着子列表可以直接访问包含它的ArrayList的底层数组和其他成员变量,这样可以方便地共享数据,避免了额外的复制或转换操作。
// 代码结构和可读性:将子列表作为内部类,可以更清晰地组织代码结构,使得代码更易读、易于理解和维护。用户通过调用ArrayList的subList方法来获取子列表,代码逻辑更加自然和直观。
16. sort(Comparator<? super E> c) 对列表中的元素进行排序的方法
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
// 该方法中需要传入Comparator类型比较器,作为排序条件。
// 方法中使用Arrays.sort()工具类,将元素数组elementData排序,无返回值,也就是说默认修改elemenData 数组,因此会有modCount修改次数。
// 与Collections.sort(List<T> list)方法相同,Collections工具类中支持使用list.sort(null); null值默认排序,再此处也可以使用null值默认排序,默认排序为从小到大顺序。
// 也可以使用接口 Comparator<String>() {} 重写 compare() 方法,实现自定义比较。
举例:
默认排序:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(33);
integers.add(55);
integers.add(22);
integers.add(11);
integers.sort(null);
// integers.sort(Integer::compareTo); 该传参是compareTo比较器,也可以通过此中方式做比较,效果一样。
System.out.println(integers);
输出结果:
[11, 22, 33, 55]
自定义倒排序:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(33);
integers.add(55);
integers.add(22);
integers.add(11);
integers.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(integers);
输出结果:
[55, 33, 22, 11]
17. isEmpty() 判断集合是否为空
@return 如果此列表不包含任何元素则返回true,反之false;
public boolean isEmpty() {
return size == 0;
}
// 比较size是否大于0;
18. clear() 移除ArrayList中所有元素,使其变为空列表。
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
// 使用for循环,将每个数组元素变为null;为size赋值为0;
四. 扩展
1. Collections工具类排序
使用Collections.sort()方法对ArrayList进行排序;
示例代码:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
// 使用Collections工具类按照从小到大顺序排序。
Collections.sort(integers);
System.out.println(integers);
输出结果:
[1, 2, 3, 4]
反转ArrayList:使用Collections.reverse()方法可以将ArrayList中的元素顺序反转;
示例代码:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
System.out.println("原顺序:"+integers);
// 使用Collections工具类反转排序。
Collections.reverse(integers);
System.out.println("反转顺序:"+integers);
输出结果:
原顺序:[3, 2, 4, 1]
反转顺序:[1, 4, 2, 3]
随机打乱ArrayList:使用Collections.shuffle()方法可以随机打乱ArrayList中的元素顺序,每次查询排序结果不一致。
示例代码:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
System.out.println("原顺序:"+integers);
// 使用Collections工具类随机打乱排序。
Collections.shuffle(integers);
System.out.println("反转顺序:"+integers);
输出结果:
原顺序:[3, 2, 4, 1]
反转顺序:[2, 3, 1, 4]
2. Collections工具类二分查找 binarySearch(List<? extends Comparable<? super T>> list, T key)
list:已排序列表
key:要查找的元素
使用该方法时,需要保证列表已按照升序进行排序,并且元素类型实现了Comparable接口或者其超类实现了Comparable接口。
示例代码:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
Collections.sort(integers);
int i = Collections.binarySearch(integers, 2);
System.out.println("元素所在位置:"+i);
输出结果:
元素所在位置:1
3. Collections工具类替换指定元素,replaceAll(List<T> list, T oldVal, T newVal)
list:是要进行替换操作的列表;
oldVal:是需要被替换的旧元素;
newVal:是替换后的新元素;
该方法会遍历列表,将所有与旧元素匹配的元素替换为新元素。如果替换操作成功进行,则返回true;否则返回false。
示例代码:
ArrayList<Integer> integers = new ArrayList<>();
integers.add(3);
integers.add(2);
integers.add(4);
integers.add(1);
System.out.println("原list:"+integers);
boolean b = Collections.replaceAll(integers, 2, 5);
System.out.println("替换后"+integers);
输出结果:
原list:[3, 2, 4, 1]
替换后[3, 5, 4, 1]
Collections还提供了很多其他有用的方法,如添加、删除、洗牌(shuffle)、查找最大/最小值等。请注意,为了使用Collections类,需要在代码中导入java.util.Collections包。
完