在正式List之前,我们先了解我们补充上篇Collection接口的拓展实现,也就是说当我我们需要实现一个不可修改的Collection的时候,我们只需要拓展某个类,也就是AbstractCollection这个类,他是Collection接口的骨干实现,并以最大限度的实现了减少此接口所需要的工作;
如上两图进行比较即可。
我们可以拓展Collection,然后提供iterator和size方法的实现即可,其中我们的iterator方法返回迭代器必须实现一个hasNext和next。
AbstractCollection<E>
是一个抽象类,不能直接实例化。如果需要使用它的方法,需要创建一个继承自它的子类,并实现Collection<E>
接口中的所有方法。
AbstractCollection<E>
提供了size()和isEmpty()方法的默认实现,但是它们都是基于iterator()方法实现的。如果子类有更高效的实现方式,可以重写这些方法。
AbstractCollection<E>
提供了contains(Object o)和remove(Object o)方法的默认实现,但是它们都是基于iterator()方法实现的。如果子类有更高效的实现方式,可以重写这些方法。
AbstractCollection<E>
没有提供add(E e)
和addAll(Collection<? extends E> c)
方法的默认实现,因为这些方法的实现方式可能因子类而异。如果子类需要实现这些方法,需要自行实现。
AbstractCollection<E>
提供了clear()和toArray()方法的默认实现,但是它们都是基于iterator()方法实现的。如果子类有更高效的实现方式,可以重写这些方法。
AbstractCollection<E>
提供了toString()方法的默认实现,但是它只是简单地将集合中的元素转换为字符串并拼接在一起。如果子类需要更复杂的字符串表示方式,可以重写这个方法。
补充:
要实现一个可以修改的Collection,我们必须重新add方法,不然就会抛出异常,UnsupportedOperationException
,iterator 方法返回的迭代器还必须另外实现其 remove 方法。
List是java中有序的、允许重复的、值可以为NULL的。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
List 接口在 iterator、add、remove、equals 和 hashCode 方法的协定上加了一些其他约定,超过了 Collection 接口中指定的约定。
List接口中的元素按照插入顺序排列,并且允许包含重复元素。
void add(int index, E element)
: 在指定位置插入指定元素。 boolean remove(Object element)
: 移除指定元素的第一个匹配项。 E remove(int index)
: 移除指定位置的元素。E get(int index)
: 返回指定位置的元素。E set(int index, E element)
: 替换指定位置的元素。int indexOf(Object element)
: 返回指定元素第一次出现的位置。 int lastIndexOf(Object element)
: 返回指定元素最后一次出现的位置。List<E> subList(int fromIndex, int toIndex)
: 返回指定范围的子列表。List接口有多个实现类,常见的有ArrayList、LinkedList和Vector等。
ArrayList是基于动态数组实现的,它支持快速随机访问和高效的插入、删除操作。适用于频繁访问元素的场景。
LinkedList是基于双向链表实现的,它支持高效的插入、删除操作,但访问元素的效率较低。适用于频繁插入、删除元素的场景。
Vector是线程安全的,它与ArrayList类似,但是性能较低。在多线程环境下,可以使用Vector来保证线程安全。
public class ListExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 添加元素
list.add("apple");
list.add("banana");
list.add("orange");
// 获取元素
String firstElement = list.get(0);
System.out.println("First element: " + firstElement);
// 修改元素
list.set(1, "grape");
// 删除元素
list.remove("orange");
// 遍历元素
for (String element : list) {
System.out.println(element);
}
// 判断元素是否存在
boolean contains = list.contains("apple");
System.out.println("Does list contain 'apple'? " + contains);
// 获取列表的大小
int size = list.size();
System.out.println("List size: " + size);
// 获取子列表
List<String> subList = list.subList(0, 2);
System.out.println("Sublist: " + subList);
}
}
AbstractList
是List接口的一个抽象实现类,它提供了List接口中的一些通用实现,使得实现List接口的子类可以更加方便地实现自己的方法。如下图所示:
注意事项:
public class MyList<E> extends AbstractList<E> {
private Object[] elements;
private int size;
public MyList(int initialCapacity) {
elements = new Object[initialCapacity];
size = 0;
}
@Override
public E get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return (E) elements[index];
}
@Override
public int size() {
return size;
}
@Override
public boolean add(E element) {
if (size == elements.length) {
resize();
}
elements[size++] = element;
return true;
}
@Override
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
E removedElement = (E) elements[index];
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
elements[--size] = null;
return removedElement;
}
private void resize() {
int newCapacity = elements.length * 2;
Object[] newElements = new Object[newCapacity];
System.arraycopy(elements, 0, newElements, 0, size);
elements = newElements;
}
}
在上述代码中 我们创建了一个名为MyList
的类,它继承自AbstractList
类,并实现了get(int index)、size()、add(E element)和remove(int index)
方法。
get(int index)
方法用于获取指定索引位置的元素。我们在方法中进行了边界检查,并将元素强制转换为泛型类型。
size()
方法返回列表的大小。
add(E element)
方法用于向列表末尾添加元素。如果列表已满,我们会调用resize()方法来扩容。
remove(int index)
方法用于移除指定索引位置的元素。我们在方法中进行了边界检查,并将被移除的元素返回。移除元素后,我们需要将后面的元素向前移动,并将最后一个位置置为null。
这个在我们面试过程中经常会碰到。
下面我们围绕ArrayList进行相关介绍:
既然说到了ArrayList的扩容,那么我们来了解一下ArrayList的扩容机制吧:
初始容量:当创建一个新的ArrayList对象时,它会分配一个初始容量。默认情况下,初始容量为10,但也可以通过构造函数指定其他初始容量。
容量增长:当ArrayList的元素数量超过当前容量时,ArrayList会自动增加其容量。容量增长的策略是通过创建一个更大的数组,并将原始数组中的元素复制到新数组中来实现的。
增长因子:ArrayList的容量增长是按照增长因子来计算的。增长因子是一个大于1的值,用于确定新容量相对于当前容量的增长量。默认情况下,增长因子为1.5,这意味着新容量将是当前容量的1.5倍,比如说10,经过一次扩容为 10 * 1.5 = 15。
扩容操作:当需要扩容时,ArrayList会创建一个新的数组,并将原始数组中的元素复制到新数组中。这个操作涉及到数组的复制,因此在扩容时会有一定的性能开销。
扩容策略:ArrayList的扩容策略是相对保守的,它会尽量避免频繁的扩容操作。当需要扩容时,ArrayList会根据当前容量和增长因子计算出一个新的容量,然后将新容量设置为ArrayList的容量。这样做的目的是为了减少扩容操作的频率,提高性能。
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 创建一个ArrayList对象
ArrayList<String> list = new ArrayList<>();
// 向ArrayList中添加一个元素
list.add("apple");
list.add("banana");
// 在指定位置插入一个元素
list.add(1, "orange");
// 删除指定位置的元素
list.remove(1);
// 删除指定元素
list.remove("apple");
// 获取指定位置的元素
String fruit = list.get(0);
// 替换指定位置的元素
list.set(0, "orange");
// 获取ArrayList中元素的数量
int size = list.size();
// 清空ArrayList中的所有元素
list.clear();
// 判断ArrayList中是否包含指定元素
boolean containsApple = list.contains("apple");
// 获取指定元素在ArrayList中的位置
int index = list.indexOf("banana");
}
}