解析java.util集合类源码(SubList)

时间:2021-11-14 17:01:33

SubList类


在1.6和1.7的api中没有给出SubList的说明,只有subList方法,返回一个List列表的子类列表

public List<E> subList(int fromIndex,int toIndex)
但在jdk的源码中,你可以搜到SubList,它在AbstractList类下,却不是内部类

先看下SubList的结构

解析java.util集合类源码(SubList)

SubList继承AbstractList抽象类,AbstractList实现了List接口,所以SubList说到底就是一个List的实现类,主要用于返回List的视图,这个视图是原List对象中的一部分,确实是一部分,直接将原List对象引用到新的子视图的List,对子视图进行改变,原List对象也会随之改变

下面就解释为什么会造成这种情况

首先看SubList中的属性域

SubList有4个自己的属相域和1个继承自AbstractList的modCount

private AbstractList<E> l;
private int offset;
private int size;
private int expectedModCount;
AbstractList l 就是存放母List的那个引用,当List对象(本编中指类似ArrayList的实现List的对象)调用subList方法时候,会调用abstractList中的subList的实现

 //AbstractList中的subList方法
public List<E> subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ?
new RandomAccessSubList<E>(this, fromIndex, toIndex) :
new SubList<E>(this, fromIndex, toIndex));
}
这里this是多态的,指的是那个外部调用subList方法的那个List对象,先判断this是否实现了RandomAccess接口,实现返回RandomAccessSublist否则返回SubList,通过构造方法将this传给SubList中l属性,在介绍构造方法时细说

int offset 开始子List在母List中的开始位置

int size 记录大小 = toIndex - fromIndex

int expectedModCount  列表的中元素添加、删除的次数,子列表的同步使用

 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;
expectedModCount = l.modCount;
}

SubList的构造方法

做3个对fromIndex和toIndex的检查后,将list 付给l,fromIndex付给offset,原list对象的modCount付给expectedModCount

其实就是将原来的list对象放到SubList中,同时将子列表的开始下标和结束下标付给SubList


public E set(int index, E element) {
rangeCheck(index);
checkForComodification();
return l.set(index+offset, element);
}
修改子列表中下标为index 的元素,同时也将原list中的元素修改,rangeCheck检查下标越界,checkFormComodification判断list是否同步

同步:在List对象返回Iterator迭代器后,不能够对List 对象进行(添加、删除)操作,否则modCount++,这时候modeCount不等于expectedmoCount。

这是在一般的List中,但是在SubList中不但要考虑迭代器和List对象的同步问题,还有母列表和子列表的同步问题,下面的checkFormComodification方法将l的modCount和subList中的expectedModCount比较,当List对象通过subList(fromIndex,toIndex)方法获取到SubList后,母List对象又调用add方法,这时母List对象中的modCount++,但子列表中的expectedModCount并没有变,造成不同步抛出异常

private void rangeCheck(int index) {
if (index<0 || index>=size)
throw new IndexOutOfBoundsException("Index: "+index+
",Size: "+size);
}

private void checkForComodification() {        if (l.modCount != expectedModCount)            throw new ConcurrentModificationException();    }

检查母列表和子类表的同步


public E get(int index) {
rangeCheck(index);
checkForComodification();
return l.get(index+offset);
}
和set方法类似,母列表开始下标是0,子列表开始下标是offset,所以获取母列表中的元素时,应加上offset

public int size() {
checkForComodification();
return size;
}
返回子列表的size

public void add(int index, E element) {
if (index<0 || index>size)
throw new IndexOutOfBoundsException();
checkForComodification();
l.add(index+offset, element);
expectedModCount = l.modCount;
size++;
modCount++;
}
将元素添加到子列表的index位置,首先检查是否同步,调用母列表的add(index,e)方法,这时候母列表的modCount会增加,更新子列表中用来判断和母列表同步的变量expectedModCount,子列表的size++,子列表的modCount++

public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = l.remove(index+offset);
expectedModCount = l.modCount;
size--;
modCount++;
return result;
}
remove方法与add(index,e)方法类似,调用母列表的remove(index)方法
protected void removeRange(int fromIndex, int toIndex) {        checkForComodification();        l.removeRange(fromIndex+offset, toIndex+offset);        expectedModCount = l.modCount;        size -= (toIndex-fromIndex);        modCount++;    }
与add(index,e)方法相似,调用母列表的removeRange(fromIndex,toIndex)方法

public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}

public boolean addAll(int index, Collection<? extends E> c) {
if (index<0 || index>size)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
int cSize = c.size();
if (cSize==0)
return false;

checkForComodification();
l.addAll(offset+index, c);
expectedModCount = l.modCount;
size += cSize;
modCount++;
return true;
}
类似

 public Iterator<E> iterator() {
return listIterator();
}
重写了iterator方法,在SubList中不管调用iterator方法,还是listIterator方法都返回的是ListIterator


public ListIterator<E> listIterator(final int index) {
checkForComodification();
if (index<0 || index>size)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);

return new ListIterator<E>() {
private ListIterator<E> i = l.listIterator(index+offset);

public boolean hasNext() {
return nextIndex() < size;
}

public E next() {
if (hasNext())
return i.next();
else
throw new NoSuchElementException();
}

public boolean hasPrevious() {
return previousIndex() >= 0;
}

public E previous() {
if (hasPrevious())
return i.previous();
else
throw new NoSuchElementException();
}

public int nextIndex() {
return i.nextIndex() - offset;
}

public int previousIndex() {
return i.previousIndex() - offset;
}

public void remove() {
i.remove();
expectedModCount = l.modCount;
size--;
modCount++;
}

public void set(E e) {
i.set(e);
}

public void add(E e) {
i.add(e);
expectedModCount = l.modCount;
size++;
modCount++;
}
};
}
listIterator方法返回的是一个ListIterator匿名类

 private ListIterator<E> i = l.listIterator(index+offset);

这个ListIterator匿名类本身操作的也是母列表中的listiterator,用i来存储这个参数

其他方法基本都是通过i这个ListIterator来对迭代进行操作,方法类似AbstractList中ListIterator,这个迭代不熟悉请看上一篇博客


更好的理解SubList类,我们可以写段程序,Debug他内部的调用过程


母列表和子列表的不同步:

    @Test
    public void test(){
        
        ArrayList list = new ArrayList();
        list.add("ee");
        list.add("ee");
        list.add("eee");
        
        List subList = list.subList(0, 2);
        list.add("ee");//母列表元素增加,母列表中的modCount增加,破坏母列表和子列表的同步
        Iterator it2 = subList.iterator();//报错,子列表和母列表不同步
        
    }
这段代码测试子列表与母列表不同步的问题

list是一个普通的ArrayList对象,subList一个从list中截取的一段子列表,我们从最开始进行debug跟踪,当list.subList()->AbstractList.subList ->这时候new SubList(ps:RandomAccessSubList也是SubList,不细说),将subList中的参数初始化,重要的同步参数expectedModCount = l.modCount = 3,此时SubList中的expectedModCount = 3,list.subList走完,此时 expectedModCount = l.modCount = 3

再看主程序中下条代码list.add(e)->ArrayList.add(e)->ArrayList.ensureCapacity(int minCapacity),这方法中modCount++,这个modCount是母列表中的modCount也就是SubList中 的l.modCount,然后添加元素,list.add(e)走完,此时l.modCount = 4 ,SubList中的expectedModCount =3

当subList.iterator()->SubList.iterator()->SubList.listIterator()->SubList.checkForComodification()这里有段代码

private void checkForComodification() {
if (l.modCount != expectedModCount)
throw new ConcurrentModificationException();
}
用母列表的modCount和SubList中expectedModCount比较相等,expectedModCount是在new SubList()时候被初始化的= 3 ,而l.modCount在list.add()时候++了=4,造成了母列表和子列表的不同步


列表与迭代器的不同步:

修改一下刚才的代码


@Test
public void test(){

ArrayList list = new ArrayList();
list.add("ee");
list.add("ee");
list.add("eee");

List subList = list.subList(0, 2);
Iterator it2 = subList.iterator();
subList.add("在这里会修改母list中的modCount");
it2.next();//这会报错,原因母列表的modCount与迭代器中的expecedModCount值不同

Iterator it = list.listIterator();
list.add("在这里会修改list中的modCount");
it.next(); //这会报错,原因母列表的modCount与迭代器中的expecedModCount值不同

}


subList方法前面有讲,大体过程和上述相同

 当subList.iterator时,在SubList中的iterator在检查同步之后,返回一个ListIterator匿名类,其中有一个参数private ListIterator<E> i = l.listIterator(index+offset);在初始化这匿名类时候,调用母列表的listIterator方法返回一个母列表的迭代器,初始化这个迭代器时,有一个int expectedModCount = modCount,这个expectedModCount是母列表迭代器中的,与SubList中的那个无关,此时母列表迭代器ListIterator中的expectedModCount = 母列表的modCount = 3

当subList.add()时候->AbstractList.add(e)->SubList.add(index,e)这里调用母列表的add方法->ArrayList.add在这里会将母列表中的modCount++ = 4,然后添加元素
再看迭代时的代码it2.next()

it2.next()调用ListIterator匿名类中的next()方法(ps:it2本身就是那个被new出来的匿名类)

public E next() {                if (hasNext())
return i.next();
else
throw new NoSuchElementException();
}
这时使用是母列表的迭代器的next方法,在next方法中checkForComodification()检查同步此时的母列表的modCount = 4,但母列表迭代器的expectedModCount =3 不同步,报错


所以说在List中使用subList要小心,因为本身SubList这个类就是原List的一个视图,改变SubList必定会改变原List