一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
概要
List和Collection最大的区别就是元素规定是有序的, 所以List在Collection的基础上添加了索引相关操作. 同时在AbstractList中对List的equals和hashCode方法进行了重定义.
List中的设计
List中几乎把Collection里所有的方法都添加了一个带有索引的重载方法, 并扩展了修改(修改索引位置元素)/索引查询/子列表视图/排序的操作. 同样AbstractList给List中的绝大部分方法都提供了默认实现, 其中clear方法的实现比较值得关注.
modCount
AbstractList除了在方法上的扩展之外同时扩展了一个modCount字段,这是个子类可选择实现的字段, 如果用户需要实现fail-fast iterators(我理解是感知list变化可以快速失败的迭代器),则只需要在add和remove方法成功时给modCount字段自增就好了.
iterator实现
AbstractList中的Itr借助于List可以根据索引操作元素的特性实现了一个公共的Iterator, 他的兄弟Set和Queue都没法做到这一点. 当然实现是一回事子类买不买账就是另外一回事了, 由于迭代器想要有更好性能和底层的数据结构是息息相关的.假如底层实现是链表结构直接使用这个默认迭代器,O(n²)的迭代效率怕是直接爆炸.
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException(e);
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ListIterator
list都有索引了,那么list需要一个有索引操作的迭代器也就合情合理了, ListIterator扩展了查询当前元素索引的方法,同时也提供往前查询的能力,使ListIterator不光能正序遍历还能倒叙或者来回遍历,当然也能修改当前元素.
public interface ListIterator<E> extends Iterator<E> {
// Query Operations
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
SubList
SubList是AbstractList中给subList方法提供的一个默认返回实现, 它的工作模式就是把操作委托给原始List进行完成,自己内部维护一套offset/size来实现对原始list操作的上下界保护. 这里挑选removeRange来学习SubList的工作模式.
- 首先SubList会先进行并发修改判断, 即自己不能落后于原始容器的修改版本,如果原始容器脱离子容器发生修改操作,那么子容器就失效了.
- 委托给root即原始容器进行删除操作.
- 更新size和modCount. 注意这里做了一些加速操作, 因为SubList的父容器可能也是SubList, 所以在做修改的时候SubList直接绕过同为视图的父容器直接对原始容器进行执行命令, 减少了许多不必要的栈帧, 如果有需要进行size和modCount更新的地方统一在一个方法处理.
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
root.removeRange(offset + fromIndex, offset + toIndex);
updateSizeAndModCount(fromIndex - toIndex);
}
private void checkForComodification() {
if (root.modCount != this.modCount)
throw new ConcurrentModificationException();
}
private void updateSizeAndModCount(int sizeChange) {
SubList<E> slist = this;
do {
slist.size += sizeChange;
slist.modCount = root.modCount;
slist = slist.parent;
} while (slist != null);
}
前文有提到过removeRange比较值得被关注, 事实上值得被关注的地方就在这里, 当子容器执行clear方法时只需要转化为removeRange操作, 语义清晰也不需要什么骚操作.
实现一个简单的List
package util.collection;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class ListExample {
public static void main(String[] args) {
SimpleList<Integer> simpleList = new SimpleList<>();
simpleList.add(1);
simpleList.add(2);
simpleList.add(3);
simpleList.add(4);
System.out.println(Arrays.toString(simpleList.toArray()));
simpleList.set(2, 2);
System.out.println(Arrays.toString(simpleList.toArray()));
simpleList.remove(2);
System.out.println(Arrays.toString(simpleList.toArray()));
System.out.println(simpleList.size);
}
public static class SimpleList<E> extends AbstractList<E> implements List<E> {
private Node<E> head;
private int size;
@Override
public E get(int index) {
checkIndex(index);
Node<E> node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node.data;
}
@Override
public int size() {
return size;
}
@Override
public E set(int index, E element) {
checkIndex(index);
Node<E> pre = null;
Node<E> node = head;
for (int i = 0; i < index; i++) {
pre = node;
node = node.next;
}
Node<E> cur = new Node<>(element);
if (pre != null) {
pre.next = cur;
} else {
head = cur;
}
cur.next = node.next;
return node.data;
}
@Override
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
Node<E> pre = index > 0 ? head : null;
for (int i = 0; i < index - 1; i++) {
pre = pre.next;
}
Node<E> cur = new Node<>(element);
if (pre != null) {
cur.next = pre.next;
pre.next = cur;
} else {
cur.next = head;
head = cur;
}
size++;
}
@Override
public E remove(int index) {
Node<E> pre = null;
Node<E> node = head;
for (int i = 0; i < index; i++) {
pre = node;
node = node.next;
}
if (pre != null) {
pre.next = node.next;
} else {
head = node.next;
}
size--;
return node.data;
}
private void checkIndex(int index) {
Objects.checkIndex(index, size);
}
}
public static class Node<E> {
final E data;
Node<E> next;
public Node(E data) {
this.data = data;
}
}
}