带你走进Java集合_ArrayList源码深入分析3

上一篇文章我们从源码角度介绍了ArrayList中两个比较相似的方法,现在我们总结一下:

1)removeAll:获取两个集合的差集,例如:list.removeAll(c),就是执行此方法,list会移除包含c的元素

2)retainAll:获取两个集合的交集,例如:list.retainAll(c),就是执行此方法,list会移除不包含c的元素

这篇文章接下来会从源码角度去分析ArrayList的迭代器。

ArrayList当中有两个迭代器,一个是iterator,一个是listIterator,下面我们逐一进行分析:

一、iterator

我们知道Collection接口继承了Iterater,所以必须实现iterator方法,下面的就是这个方法的具体实现。

public Iterator<E> iterator() {
        return new Itr();
    }

这个方法非常的简单,直接创建了一个ArrayList的一个内部类Itr

private class Itr implements Iterator<E> {

Itr实现了Iterator接口,

1)Itr的几个重要的属性

第一个重要的属性:cursor游标

int cursor;    

cursor是下一个元素的下标,这是Itr最重要的一个属性了,其实他就类似于一个游标

第二个重要属性:lastRet最后一个元素的下标

int lastRet = -1; // index of last element returned; -1 if no such

2)Itr重要的方法,主要实现Iterator

第一个方法:hasNext

public boolean hasNext() {
            return cursor != size;
        }

其中size是ArrayList中元素数量,此方法是迭代器中判断还有没有下一个元素,就是游标cursor是否移动到了最后,如果移动到了最后,则返回false,否则返回true

第二个方法:next

public E next() {
            checkForComodification();------------------------------------❶
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;--------------------------------------------❷
            return (E) elementData[lastRet = i];-----------------------❸
        }

❶在迭代器中体现了一个机制,就是fast-fail机制,那就是在迭代时,如果有对ArrayList调用add(),remove(),set()等能改变元素的方法时,会立刻抛出ConcurrentModificationException异常,就是说在迭代的时候不能修改ArrayList

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

modCount我们在这篇文章已经介绍过了,只要修改ArrayList里面的元素,就会使modCount++.如果你用for循环来代替iterator,则没有fast-fail的机制。

接下来int i = cursor,把游标赋值给i局部变量。如果i>=size,则会抛出异常,所以利用迭代器时要用hasNext()和next()结合使用,否则就会出现cursor>=size,导致出现异常。

❷就是把游标cursor向后移动一位,❸就是把当前位置赋值给lastRet,然后返回当前元素。

所以总结一下next方法

1.判断在迭代时,是否有对ArrayList进行修改,如果修改则直接抛出异常,是通过modCount判断的。
2.把ArrayList底层数组赋值给elementData,
3.然后移动游标,
4.把当前位置的下标赋值给lastRet,然后返回当前元素

从next方法可以看出,游标的移动,lastRet的赋值都是在这个方法完成的

第三个方法:remove

public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

所以迭代有一个重要的作用,就是在迭代时可以删除,但是必须调用next后才能调用remove方法,因为如果不调用next,lastRet就是默认值-1,直接调用remove则会抛出异常,仔细想想不难理解,Itr迭代是通过游标cursor获取的,游标不移动,我们就无法获取到集合里面的元素,就不知道删除哪一个元素

所以总结一下remove方法:

1.判断lastRet是否大于等于0,如果不是则抛出异常,就是没有迭代元素,也就不知道要删除哪一个元素了。
2.判断一下在调用remove方法时,是否ArrayList是否修改过。
3.从ArrayList删除下标lastRet的元素
4.将lastRet下标赋值给游标cursor,这句话的意思就是,删除了当前元素,游标要向前移动一位。
5.将lastRet=-1,调用remove一次,就不能在删除了,只有在调用next,才能在调用remove

Itr主要理解这些内容就可以了,我们要知道ArrayList的迭代有这些特性

1.进行迭代进行遍历元素,当多个线程修改ArrayList时,就拥有fast-fail机制,则普通的循环则没有这个特性

2.在迭代的时候可以调用remove方法删除当前元素,而普通的循环如果调用删除,则会出现错误。

二、ListIterator

ArrayList中有两个方法能够获取ListIterator.

public ListIterator<E> listIterator() {
        return new ListItr(0);
    }
public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

private class ListItr extends Itr implements ListIterator<E> {

ListIterator继承了Itr,我们知道Itr只能向后迭代,游标只能往后走,而ListIterator则在Itr基础上增强了cursor的功能,不但能够向后移动,而且也能向前移动,能够向前遍历了。

ListItr(int index) {
            super();
            cursor = index;
        }

新创建时就设置了游标cursor=index.

ListIIterator相对Itr,新增了如下的方法:

第一个增强方法:hasPrevious

public boolean hasPrevious() {
            return cursor != 0;
        }

此方法和hasNext相对,判断游标是否移动到了首位。

第二个增强方法:nextIndex:获取当前游标的位置

public int nextIndex() {
            return cursor;
        }

第三个增强方法:previousIndex,获取游标的前一个下标

public int previousIndex() {
            return cursor - 1;
        }

第四个增强方法:previous,获取游标上一个元素

public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

和next作用相似,所以hasPrevious和previous要结合使用

第五个增强方法:set,覆盖下标lastRet的元素

public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

第六个增强方法:add 

public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

从上面的方法我们可以总结ListIterator的作用

1.游标即可以向前移动,也可以向后移动

2.迭代的时候可以进行增删改。

通过上面的分析,可以看出ArrayList具有迭代器的功能,我们总结一下迭代器和普通的循环的区别

1)迭代器迭代的时候可以fast-fail机制,而普通方法则没有

2)迭代器可以在迭代的时候可以移除当前迭代的元素,ListIterator可以add,set,而普通循环时不能进行增删改。

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/shaotianqiang/article/details/80607943
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞