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()) 从后面开始匹配

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
  • 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原则
      • 核心代码
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、AbstractList list:当前的集合
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 AbstractList l,这个是个集合,有啥用后面再看,注意哈,这里没赋值而且还是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)
      • 开始不能大于结束,等于就没啥操作,嘎嘎嘎
  • 最后就是赋值逻辑,咱们一行一行看

    • 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) 范围的新集合
但是对于新集合的操作,实际上仍旧是相对于旧集合而言,那么改变新集合,实际上旧的集合会被改变
心心念的几个方法也没实现,还是要到其他实现类看一下

posted @ 2019-10-12 14:55  贾佳琪  阅读(122)  评论(0)    收藏  举报