AbstractList源码分析
AbstractList源码分析
1、课前小激动
快到具体实现类了,加油吧。
ArrayList前最后一层迷雾,马上就要解开了。
该类继承了AbstractCollection,实现了List 接口
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
2、关键字
-
skeletal:骨骼
- 提供了List的最简单实现,使用者可以根据自己的需要进行方法重写
-
For sequential access data (such as a linked list)
- 如果想要实现顺序的访问数据(例如链表),那么应该实现AbstractSequentialList,而不是当前类
-
unmodifiable and modifiable
- unmodifiable 只需要重写 get(i) 和 size() 方法即可
- modifiable但是没有长度变化, 只需要修改set(int ,E) 即可
- modifiable且有长度变化,那么就需要重写add(int, Object) 和 remove(int) 方法了
-
需要提供两个构造方法:
- no args
- collection
-
有没有 fail-fast机制?
3、方法
3.1 未实现的方法
-
add(E e):在集合最后添加指定元素
- 依赖add(size(), e) 方法
- 如果没有重写add(int, E),将会抛出异常
-
add(int ,E):在制定位置添加元素
- 默认实现会抛出异常
-
E get(int index):获取指定索引的元素
- 没有默认实现
-
E set(int index, E element):替换指定索引位置的参数
- 没有默认实现,但有很多异常
-
E remove(int index):移除指定位置的元素
- 没有默认实现,要和Collection的remove(Object obj) 区分开来
- remove(Object obj)实现是基于iterator的
- 会返回移除的元素
3.2 Search Operations
-
int indexOf(Object o):获取指定元素的索引,index lowest
- 使用是ListIterator,可以双向遍历,效率应该低
- 逻辑梳理
- 会判断指定元素是否为空
- 返回的索引值,是当前元素的it.previousIndex()
- 需要注意的是,使用前必须调用it.next(),不然会死循环
- 找不到的话返回 -1
-
int lastIndexOf(Object o):从后往前第一个匹配到的
- ListIterator
it = listIterator(size()) 从后面开始匹配
- ListIterator
3.3 Bulk Operations
-
clear():清空集合
- 实现该方法的时候需要重写remove(int index) or removeRange(int fromIndex, int toIndex)
-
protected void removeRange(int fromIndex, int toIndex):移除from - to之间的元素
- 如果fromIndex == toIndex,则对原集合没有影响
- 具体实现
- 获取当前集合的 listIterator(fromIndex),从from开始操作
- for (int i=0, n=toIndex-fromIndex; i<n; i++),遍历 to -from之间的元素
- it.next()、it.remove()
- 通过观察可以看到,这个方法还是比较耗时间的,因为先使用iterator遍历,然后又进行循环删除
-
boolean addAll(int index, Collection<? extend E> c):从指定位置插入指定集合
- 该位置后的元素应该后移
- 利用的是list的add(int index, object e)
- 具体实现
- rangeCheckForAdd(index):校验是否在list范围内
- 使用增强for循环循环 c
- 每次新增一次元素,这里并未判断是否全部新增成功,所以需要注意当返回值为false的时候只能说明本次批量插入存在未成功的,并不能保证全部失败。
-
private void rangeCheckForAdd(int index):私有方法
- 检验指定的位置是否不再集合范围内:< 0 或者 > list.size()
-
Iterator
iterator():获取当前集合迭代器 - 这里需要开副本了,在阅读该类注释的时候提了一句,子类可以不必重写iterator,因为该类已经比较好的实现了iterator,我们发现实际上ArrayList也确实大致按照该类的方式重写的iterator。所以这里需要着重介绍以下
-
ListIterator
listIterator() - 获取当前集合的listIterator,这里是默认的
- return listIterator(0) 实际上调用的是 listIterator(int size)
-
ListIterator
listIterator(final int index):获取指定位置的list迭代器 - 观察参数哈,竟然是个final的,也就是获取之后不允许修改这个值
- 实现原理
- rangeCheckForAdd(index); 判断下标是否越界
- return new ListItr(index); 继续开副本
-
List
subList(int fromIndex, int toIndex):返回from-to之间的数组 - 这个是List独有的subList方法,但是此处 又一次创建了一个内部类
return (this instanceof RandomAccess ?
new RandomAccessSubList<>(this, fromIndex, toIndex) :
new SubList<>(this, fromIndex, toIndex));
我们分析下这段代码哈。我们看到,this instanceof RandomAccess 这段代码,这是啥意思呢?还记得我们一开始介绍这个实现类的时候,类注释提了一个:backed by a "random access" data store (such as an array),这段意思是,该类提供了类似数组那样,随机访问元素的方式,那想想数组啥特点?不就是可以根据元素索引任意访问吗?那么是不是可以解释random access就是类似于这个功能呢?那么不妨先看看RandomAccess接口的解释,以及当前类有没有具体实现呢?
如果当前类实现了RandomAccess接口,那么下面会执行:new RandomAccessSubList<>(this, fromIndex, toIndex)
否则就会执行:new SubList<>(this, fromIndex, toIndex))
继续开副本啦
RandomAccess接口
这是一个标记接口,表示实现此接口的类支持随机访问:random access例如,
ArrayList
还说啦,for(int i = 0;xxx) 效率要比迭代器高,我也不实验了。高就高吧,要记住就行啦
- boolean equals(Object o)
- 来自Object的equals,判断两个集合是否相等
- 相等原则
- 指定元素是集合
- 大小相等
- 对应的所有元素都相关(好难呀)
- 代码
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
如果指定集合与当前集合是一个,直接返回
true
如果指定的对象不是集合,直接false
分别获取两个集合的 listIterator
e1.hasNext() && e2.hasNext()这个判断实际上就带着大小判断了
后面的判断就是:对应元素是否相等,是否同时都没有下一个元素了
- int hashCode()
- 记得List中好像是提过,hashCode有默认方式就是这种,对,注释有;
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
- removeRange(int fromIndex, int toIndex)
- rangeCheckForAdd(int index)
- outOfBoundsMsg(int index)
- protected transient int modCount = 0;
- 万恶之源出现了,哈哈哈,注意transient 关键字标注的属性是不会序列化的
3.4 内部类介绍
3.4.1 Itr implements Iterator
Itr 是AbstractList对于迭代器的实现,这里算是给List相关的实现类定了一个基调。
想到Iterator,我们肯定会想到 hasNext()、next()、remove()这些,那么我们下面看一下具体怎么实现的吧
3.4.1.1 类参数
- int cursor = 0;
- cursor 翻译是光标,顾名思义啦,就是当前集合所处的索引位置,默认肯定是 0,但是后面肯定有修改
- int lastRet = -1;
- 最后调用next或previous(此处只有next)返回的元素索引
- 这里表示调用next的时候会修改该参数,就是当前的cursor
- 如果通过调用迭代器删除元素,那么请重置该元素为 -1
- 这个也很简单,删除的时候把他置为 -1
- 最后调用next或previous(此处只有next)返回的元素索引
- int expectedModCount = modCount;
- 这个就很有意思了,遵从fail-fast原则。当出现expectedModCount != modCount的时候就表示出现了不期望的修改(官方解释是并发修改,我觉不太好,还是根据名字,不期望的修改比较好)
- 并发的意思是,我在使用iterator操作集合的时候,又有操作去修改集合的结构,例如集合本身的remove(Object o) 或者 remove(int index),那么这对于遵从fail-fast的集合而言是不被允许的,那么就会抛异常。但是我觉得这也不能解释为并发吧。
- 迭代器 next()、remove()执行前都会被该参数进行校验,如果不一致就会抛出ConcurrentModificationException
3.4.1.2 方法
- hasNext:来自于Iterator接口,判断是否还有下一个元素
- 原理:
- 就是判断 cursor 是不是等于size(),不等则表示没到最后,但是要保证不能大于呀。
- 原理:
- E next():来自Iterator,获取下一个元素
- 原理:
- checkForComodification();
判断 modCount != expectedModCount是不是相等,【不等抛异常】,fail-fast原则 - 核心代码
- checkForComodification();
- 原理:
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
第一行是将当前i指向当前指针
调用get(index i) 获取当前值,需要实现类自己实现
lastRet = i,将lastRet 指向当前指针
cursor = i + 1; 指针指向下一个
返回
过程中有可能会发生数组下标越界异常,有趣的是异常后还会判断是不是出现了不期望的修改,然后抛出了NoSuchElementException() 异常。
- void remove():来自Iterator,移除元素
- 源码
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();
}
}
第一行判断上一个位置是不小于0,实际上不就是判断有没有调用next() 方法了么
第二个是fail-fast的校验
AbstractList.this.remove(lastRet) 利用的是 list的remove方法,这个应该是比collection定义的remove(obj) 有效率
将当前的光标位置前移一位,因为删除了么。
将lastRet = -1,符合规定,最后因为已经修改了集合结构,所以需要对expectedModCount
重写赋值,避免后面在遍历的时候出错。这也保证了iterator的remove() 方法时安全的
- final void checkForComodification()
- 上面有调用的,就不解释了哈
3.4.2 ListItr 源码
这个内部类就是对于ListIterator的实现,其实也是List特有的,双向遍历器
3.4.2.1 类定义
ListItr extends Itr implements ListIterator<E>- 这个类定义就蛮有意思的啦,因为
Itr已经实现了几个方法喽,那么就不需要在重复造轮子啦
3.4.2.2 再看下构造方法
ListItr(int index) {
cursor = index;
}
提供了一个带有一个所以你的构造方法,cursor 是来自Itr的哈
可以根据需要去创建双向遍历器的位置
3.4.2.3 方法
-
boolean hasPrevious()
- 实现比较简单,就是根据 cursor != 0 也就是表示是否为第一个
- 那么如果是默认的例如 listIterator() 方法就不能用这个方法喽
-
E previous():当前元素的前一个元素
- 上面介绍了next() 的实现是根据get(i) 实现的,那么previous呢?
- 代码:
public E previous() {
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
方法首先会判断是不是出现了不期望的修改,属性都是继承Itr,所以Itr没有把属性定义为private
其次会把当前光标前移int i = cursor - 1,同样是根据get(i)获取元素;
lastRet = cursor = i; 将当前光标、元素的位置都前移
返回最后的值,后面的异常判断与next类似
-
int nextIndex():返回下一个索引
- 返回的竟然是 cursor,是不是说明需要先调用一个next() 然后才能调用nextIndex()
-
int previousIndex():返回当前元素前一个索引
- 有可能是-1,因为构造方法传进来的是 0
- 如果我调用previous后在调用一次,会出现啥情况?啦啦啦多减了一次是不是。
- 也就是要求必须在previous()前调用一次
-
set(E e):替换指定位置的值
- 这个肯定是依赖 List的set(int index) 方法
- 源码
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
首先会判断前面是不是调用过remove方法或者没有调用next()方法,那么就会出现 lastRet = -1,所以要求我们不能再remove() 方法后调用set()方法,同时必须先移动光标然后在set
下面是fail-fast,不说啦
具体实现是,看吧,是set(lastRet, e) 是不是,是把前一个调用next()或previous的元素替换掉。
最后呢,还是那个,只要对集合进行了修改,那么modCount的值就会改变,所以这里还是要重新赋值
- add(E e):添加元素,注意位置哈
- 直接贴源码啦
public void add(E e) {
checkForComodification();
try {
int i = cursor;
AbstractList.this.add(i, e);
lastRet = -1;
cursor = i + 1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
当当当,最开始又是校验是不是出现了modCount的值,当然咯,add方法肯定是要修改集合的,所以先校验喽
int i = cursor 获取当前的索引,注意哈,当我们创建ListIterator的时候,我们是传过来一个index的,也就是当前的cursor。那么可以认为我们此时是要在index的位置添加个元素喽
AbstractList.this.add(i, e) 这个就是调用集合的添加方法了
咦,lastRet = -1这个看到没,也就是表示add(e) 方法后不允许紧接着调用remove()啦,注意这个用法哈。
最后是把光标后移一个,因为当前元素已经被我们添加成新的元素的,那么如果还想cursor指着我们未修改前的那个元素,那肯定要右移一个。
最后最后还是要记得把我们的expectedModCount重写赋值
3.4.3 RandomAccessSubList 源码
3.4.3.1 类定义
class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {
这个还是蛮多的,他首先继承了SubList(这也是一个内部类),又实现了RandomAccess,表示支持随机访问
3.4.3.2 构造方法
RandomAccessSubList(AbstractList<E> list, int fromIndex, int toIndex) {
super(list, fromIndex, toIndex);
}
构造方法接收三个参数:
1、AbstractListlist:当前的集合
2、int fromIndex:开始的索引
3、int toIndex:结束的索引
最后还是要调用SubList的构造方法,咱们下面分析一下
3.4.3.3 方法解释
就一个方法,还是比较好解释的
public List<E> subList(int fromIndex, int toIndex) {
return new RandomAccessSubList<>(this, fromIndex, toIndex);
}
这个方法最终还是创建一个RandomAccessSubList 对象,这也是一个列表,那么关键就在于SubList的实现了
3.4.4 SubList 源码
3.4.4.1 类定义
class SubList<E> extends AbstractList<E>
这不就是一个集合么,竟然内部类又继承了自己,哇哇哇。烦得很
sub的意思不就是截取么,那么现在先猜测一下这个类是把原集合截取返回
3.4.4.2 参数列表
private final AbstractList<E> l;
private final int offset;
private int size;
三个成员变量,咱们一个一个解释
private final AbstractListl,这个是个集合,有啥用后面再看,注意哈,这里没赋值而且还是final
offset也是final,可以通过字面意思理解为偏移量
size表示当前还是截取以后的呢?那咱们下面要根据构造方法看一下
3.4.4.3 构造方法
吐槽一下哈,自己说实现Collection推荐两个构造方法,你看看,几个没有按照格式的啦。哈哈哈,开个玩笑哈
SubList(AbstractList<E> list, int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > list.size())
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
l = list;
offset = fromIndex;
size = toIndex - fromIndex;
this.modCount = l.modCount;
}
简单分析下
-
上面RandomAccessSubList中有调用这个类的构造方法,我们可以知道,这个类的构造方法支持三个参数
- 1、list:当前的集合(需要被改变的)
- 2、fromIndex:开始索引;
- 3、toIndex:结束索引
-
判断逻辑
- 第一个判断: if (fromIndex < 0)
- 表示开始不能小于零,也就是越界
- 第二个判断: if (toIndex > list.size())
- 同样呢,结束不能大于集合的大小
- 第三个判断: if (fromIndex > toIndex)
- 开始不能大于结束,等于就没啥操作,嘎嘎嘎
- 第一个判断: if (fromIndex < 0)
-
最后就是赋值逻辑,咱们一行一行看
- l = list;把当前集合赋值给 l ,记住只能赋值一次哈;
- offset = fromIndex; 偏移量等于开始的索引,也是只有一次哈
- size = toIndex - fromIndex; 大小就等于 结束 - 开始喽
- this.modCount = l.modCount 当当当,这是啥来着!竟然给modCount赋值啦,这是要逆天么
上面就是SubList类的构造方法,里面做了两件事
- 第一件事就是判断截取的返回是否符合要求
- 第二件事呢就是给成员变量赋值,后面有用处,l和offset,后面不能改了哈
3.4.4.4 方法介绍
因为是继承AbstractList,其实有些方法咱们上面已经介绍过了。但是呢,咱们着重看下get()、set()、add() 等方法的实现哈。
- set(int index, E element)
- 看了源码就总结出一句话:不负责任
public E set(int index, E element) {
rangeCheck(index);
checkForComodification();
return l.set(index+offset, element);
}
这不就相当于没实现么。。。
除了前面添加 是否越界还有fail-fast的校验,后面仍旧是调用AbstractList的set方法,害得我白激动一下
咦,注意看set(index + offset) 这个,好像有点意思哈,强行解释一波
1、offset这里我理解为偏移量,虽然我们SubList表面上看是一个新集合,但是实质上仍旧是原集合,那么offset就是原数组相对的位置,
2、offset + index,这就确认了需要修改的元素的位置,offset对于SubList而言就是0,但是对于原数组而言就是偏移量。理解了么有。这么说吧,就是我想要修改SubList中索引为index的元素,但是我实际上修改的确实原集合中索引为 index + offset的元素,因为subList相对于原数组而言便宜了 offset。
哈哈哈,终于解释清楚了。反正下面方法理解都是一样,我想要修改index位置的元素,实际上是修改的原集合index + offset位置元素
-
E get(int index)
- 我不想贴源码了,就是调用原集合的get(i),i = index + offset
-
int size()
- size = toIndex - fromIndex
-
add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
checkForComodification();
l.add(index+offset, element);
this.modCount = l.modCount;
size++;
}
当当,这个方法还是比较有意思的。前面校验不解释啦,后面调用 原集合的add(int index, E element)方法添加,index = index + offset
注意哈,因为调用了 add() 方法,所以此处对于 modCount是需要重新赋值的,这个好有意思哈,你看哈,this.modCount是从父类继承来的,l.modCount是父类的。嘎嘎嘎
最后要把大小 ++
-
E remove(int index)
- 大致与上面一样,不解释喽
-
void removeRange(int fromIndex, int toIndex)
- 这个方法实现里面有个:l.removeRange(fromIndex+offset, toIndex+offset)
- 这个就是凡事操作原集合索引的,加上offset就没错喽
-
addAll(Collection<? extends E> c)
-
addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
int cSize = c.size();
if (cSize==0)
return false;
checkForComodification();
l.addAll(offset+index, c);
this.modCount = l.modCount;
size += cSize;
return true;
}
没啥解释的。。。
-
Iterator
iterator() - 这个直接返回的就是 listIterator()
- 都不掩饰了
-
ListIterator
listIterator(final int index) - 自己实现了一遍 listIterator
- 但感觉确实没啥新意,原理就是获取原来集合的ListIterator,然后巴拉巴拉
-
List
subList(int fromIndex, int toIndex) - 诺,自己把自己new了一下
不写啦,后面几个方法都是校验的,不想写啦,总结一下吧
3.4.4.5 总结
感觉又把AbstractList看了一遍,那么这个类有啥用呢?
个人理解就是,根据你传过来的参数,把集合分割成(from, to) 范围的新集合
但是对于新集合的操作,实际上仍旧是相对于旧集合而言,那么改变新集合,实际上旧的集合会被改变
心心念的几个方法也没实现,还是要到其他实现类看一下

浙公网安备 33010602011771号