ListIterator源码逐条解析

点点圈 提交于 2020-03-08 18:15:40

一家之言 姑妄言之 絮絮叨叨 不足为训

ListIterator接口注释翻译:

   列表的迭代器,它允许程序员以任意方向遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置。ListIterator没有当前元素。它的游标位置总是位于调用previous()返回的元素和调用next()返回的元素之间。长度为n的列表的迭代器有n+1个可能的光标位置,如下面的插入符号(^)所示:

Element(0) Element(1) Element(2) Element(3) Element(n-1)
^ ^ ^ ^ ^

   注意,remove和set(object)方法不是根据游标位置定义的。它们被定义为对调用next()或previous()返回的最后一个元素进行操作。
   该接口是Java集合框架的成员。


笔者废话:

   这个接口的注释说的我也是云里雾里,但是我们大概可以从中略窥一二。首先它说明了我们这个接口是可以从任意方向遍历的。如果你仔细看过这个接口内的方法声明,也就明白了这个“任意方向”的含义。通俗来说,它有了所谓的“前驱节点”和“后继节点”。
   我相信看到这两个名词你可能就明白了这个接口的本意,它就是一个双向链表,不过我们平常也叫它双链表
   其次,它也告诉我们这个接口的游标是不指向元素的,它位于前驱节点后继节点之间。不过我还是要说,前驱节点后继节点之间不是本身还有元素吗?我实在不理解这个作者在写这段注释的含义。但是不可否认的是,它确实没有当前元素。这个还是通过声明的方法就可以得出的结论。


ListIterator接口信息:

public interface ListIterator<E> extends Iterator<E>

   我们可以清楚的看到,ListIterator是一个接口,而且这里还规定了泛型机制。同时,这个接口继承了Iterator接口,关于Iterator接口的解析请参照Iterator源码逐条解析这篇文章吧。


ListIterator接口方法信息:

/**
 * 如果此列表迭代器在正向遍历列表时包含更多元素,则返回true。
 * (换句话说,如果next将返回一个元素而不是抛出一个异常,则返回true。)
 *
 * @return 如果在正向遍历列表时,列表迭代器有更多的元素则返回true
 */
boolean hasNext();

   这里覆写了Iterator接口中的hasNext()方法。从注释上来说它更多地描述了一个正向遍历的概念,当然,这是因为我们这个接口还有反向遍历的方法hasPrevious()。其余的解释和Iterator源码逐条解析里说的一样,不过我还是复制到下面供参考。不过如果已经阅读了我提到的这篇Iterator源码逐条解析文章,那么下面两段忽略即可。
   这个方法意在询问当前遍历的容器中是否含有下一个元素。这里会涉及到一个指针操作,当然,我们java宣称的可是没有指针,所以,这我们可以把它称之为“指向”。一般来说,如果我们使用了某个集合的ListIterator对象后,会在这个集合的上方出现一个空值,而我们的ListIterator对象就指向这个空值
   但是需要注意的是,这个方法仅仅是询问下一个元素是否存在,此时并不移动指向位置,只有调用了next()方法后,这个指向位置才会进行移动,即,移动到下一项。

/**
 * 返回列表中的下一个元素并向前移动光标位置。可以重复调用此方法来
 * 遍历列表,也可以将此方法与对previous的调用混合在一起来回调用。
 * (注意,交替调用next和previous将重复返回相同的元素。)
 *
 * @return the next element in the list
 * @throws NoSuchElementException if the iteration has no next element
 */
E next();

   这里覆写了Iterator接口中的next()方法。我们从注释上可以看到,这个方法可以和previous()方法重复使用。不过这里也说明了如果交替使用的话将返回相同的元素。这个倒是容易理解,你遍历前又遍历后,便利后又遍历前,一直魔力转圈圈。那么以下两段依旧引自Iterator源码逐条解析中的解析。如无疑问,那么就跳过。
   这个方法是与hasNext()方法遥相呼应的。它代表着返回迭代器中的下一个元素。一般来说,我们都会通过hasNext()方法来判断是否有下一个元素,一旦有,那么就调用next()方法展示(取出)这个元素。
   重复上面的一段话:调用了next()方法后,容器的指向位置会进行移动,即,移动到下一项。

/**
 * 如果该列表迭代器在反向遍历列表时包含更多元素,则返回true。
 * (换句话说,如果previous返回一个元素而不是抛出一个异常,则返回true。)
 *
 * @return 当以反向遍历列表时,如果列表迭代器有更多的元素则返回true
 */
boolean hasPrevious();

   好的,这个是我们ListIterator接口定义的一个新方法。它和hasNext()方法是对应的。一个是正向遍历(hasNext()),一个是反向遍历(hasPrevious())。
   这个方法意在询问当前遍历的容器中是否含有上一个元素。它依旧会存有一个指向元素的指向。不过它和hasNext()方法一样,仅仅是询问上一个元素是否存在,此时并不移动指向位置,只有调用了previous()方法后,这个指向位置才会进行移动,即,移动到上一项。

/**
 * 返回列表中的前一个元素,并将光标向后移动。可以反复调用此方法以
 * 向后遍历列表,也可以将此方法与对next的调用混合在一起来回调用。
 * (注意,交替调用next和previous将重复返回相同的元素。)
 *
 * @return 返回列表中的前一个元素
 * @throws 如果迭代没有先前的元素则抛出NoSuchElementException异常
 */
E previous();

   这个是我们ListIterator接口定义的一个新方法。我们从注释上可以看到,这个方法可以和next()方法重复使用。不过,需要注意的是,注释中在解释这个方法的时候使用了backwards,即向后遍历。其实就是我们通常意义上的向前遍历(向左移动)。这里不是翻译的问题,而是理解性的问题。那么以下两段仿照Iterator源码逐条解析中的解析。如无疑问,那么就跳过。
   这个方法是与hasPrevious()方法遥相呼应的。它代表着返回迭代器中的上一个元素。一般来说,我们都会通过hasPrevious()方法来判断是否有上一个元素,一旦有,那么就调用previous()方法展示(取出)这个元素。
   重复上面的一段话:调用了previous()方法后,容器的指向位置会进行移动,即,移动到上一项。

/**
 * 返回将由后续调用next返回的元素的索引。(如果列表迭代器位于列表的末尾,
 * 则返回列表大小。)
 *
 * @return 对next的后续调用将返回的元素的索引,或者如果列表迭代器位于
 * 列表末尾,则为列表大小
 */
int nextIndex();

   这个是我们ListIterator接口定义的一个新方法。这个方法在注释中说是“返回将由后续调用next返回的元素的索引”,实际上它返回当前元素的游标,也就是当前元素的索引值(这个元素可还没遍历过哦)。通俗来说,当我们获取到需要遍历集合的ListIterator时,这个时候我们的游标cursor肯定指向了0。那么这个nextIndex()返回的就是0。记住,这里返回的,可是未遍历过得元素索引,因为我们的例子中并没有提到调用next()后再调用nextIndex()

/**
 * 返回将由后续调用previous返回的元素的索引。(如果列表迭代器位于列表的开头,
 * 则返回-1。)
 *
 * @return 对previous后续调用将返回的元素的索引,如果列表迭代器位于
 * 列表开头,则为-1
 */
int previousIndex();

   这个方法的解释其实和nextIndex()相似,因为其功能相反。它返回的就是当前元素游标的前一项。可能有些许绕。也就是说,它返回的是我们当前最近一次遍历过的元素的索引值。也就是这个方法的使用,起码在第一次获取到ListIterator时,必须先进行next()行为,使得我们的游标cursor0变为1,然后再调用previousIndex()方法才能返回0这个索引,否则则返回-1

/**
 * 从列表中删除next或previous返回的最后一个元素(可选操作)。此调用
 * 在每次调用next或previous时只能执行一次。只有在最后一次调用next
 * 或previous之后没有调用add时,才可以执行此操作。
 *
 * @throws 如果此列表迭代器不支持删除操作则抛出
 * UnsupportedOperationException异常
 * @throws 如果没有调用next或previous,或者在最后一次调用next或
 * previous之后调用了remove或add则抛出IllegalStateException异常
 */
void remove();

   这个remove()方法挺有意思。
   首先,这个方法只有在调用了next()或者previous()方法之后才可调用。而且你中途还不能调用add(E e)操作。这个不难理解,一般我们在调用了next()或者previous()方法之后游标进行变动。而删除的就是跟这个游标cursor相关的所在位置的元素。当然,我们这里也不确定是哪一个,因为这毕竟是个接口说明,还未到实现本接口的具体实现。所以这里我们只能进行猜测。
   不过通过这种亦步亦趋的操作属性,我们也能得出这里最好不要进行添加行为。因为你可能破坏了当前的数据结构,造成删除错误的结果
   其次,当我们没有调用next()方法或previous()方法的话,或者最后一次调用next()方法或previous()方法之后调用了remove()方法或add(E e)方法会抛出IllegalStateException异常。关于这个解释我感觉还是看相关实现类的实现要好。因为前半句易理解,如果你不调用next()方法或previous()方法的话,游标不移动它都不知道删谁。但后半句却不是那么容易理解。所以,我们还是看具体方法的实现吧(放心,这个可不是一个小小接口说明就能搞清楚的~

/**
 * 将next或previous返回的最后一个元素替换为指定的元素(可选操作)。
 * 只有在最后一次调用next或previous之后调用了remove或add,才可以执行此调用。
 *
 * @param e 用来替换next或previous返回的最后一个元素的元素
 * @throws 如果此列表迭代器不支持set操作则抛出
 * UnsupportedOperationException异常
 * @throws 如果指定元素的类阻止将其添加到此列表中则抛出
 * ClassCastException异常
 * @throws 如果指定元素的某些方面阻止将其添加到此列表中则抛出
 * IllegalArgumentException异常
 * @throws 如果没有调用next或previous,或者在最后一次调用next
 * 或previous之后调用了remove或add则抛出IllegalStateException异常
 */
void set(E e);

   这个方法的注释让我百思不得其解,但是这个set(E e)方法的本意我们还是可以大致弄清楚的,就是当你调用了next()方法或者previous()方法之后,如果你用了本方法,那么就会修改当时你获取的这个元素的值。譬如我们有一个数组:[“a” , “b” , “c” , “d”]。
   当我们初始化拿到Iterator的时候,开始使用next()方法,这个时候返回的原始应该是"a"吧?对,然后你再调用set("c")。这个时候你的数组就变成了:[“c” , “b” , “c” , “d”]。同理,调用previous()也是这种情形。
   具体的,我们还是看其实现类是如何进行操作的。

/**
 * 将指定的元素插入列表(可选操作)。元素被插入到next将返回的元素之前(如果有的话),
 * 以及previous将返回的元素之后(如果有的话)。(如果列表不包含元素,则新元素将成为
 * 列表中惟一的元素。)新元素被插入到隐式游标之前:对next的后续调用不受影响,而对
 * previous的后续调用将返回新元素。(这个调用将增加一个调用nextIndex或
 * previousIndex返回的值。)
 *
 * @param e 要插入的元素
 * @throws 如果此列表迭代器不支持add方法则抛出
 * UnsupportedOperationException异常
 * @throws 如果指定元素的类阻止将其添加到此列表中则抛出
 * ClassCastException异常
 * @throws 如果此元素的某些方面阻止将其添加到此列表中则抛出
 * IllegalArgumentException异常
 */
void add(E e);

   这个方法与其说是“添加”,我觉得不如说是“插入”来的更贴切些。
   它本意是要在调用了next()方法之后进行插入。抑或是在调用了previous()方法之后进行插入。放心,你没有看错。都是在之后,在哪个之后呢?在你调用了next()方法或previous()方法返回的元素之后。
   我们拿next()方法举例
   首先我们有一个容器(不一定是数组):

1 2 3 4 5

   当我们两次调用next()方法之后,你所返回的元素应该是下面^所指向的那个:

1 2 3 4 5 6
^

   这个时候你开始利用add(E e)方法进行插入,比如add(10)。它会插入到哪里呢?它会插入下面这个地方:

1 2 3 4 5
^

   最后你得到的结果是这样的:

1 2 10 3 4 5
^

   我们再拿previous()方法举例
   首先我们有一个容器(不一定是数组):

1 2 3 4 5

   当我们三次调用next()方法之后紧接着调用一次previous()方法。你所返回的元素应该是下面^所指向的那个:

1 2 3 4 5 6
^

   这个时候你开始利用add(E e)方法进行插入,比如add(10)。它会插入到哪里呢?它会插入下面这个地方:

1 2 3 4 5
^

   最后你得到的结果是这样的:

1 2 10 3 4 5
^

   你会发现,这个方法无论在哪个场景下使用,都会添加到返回元素的后面。但是并不代表注释翻译的不正确,它尽量的描述类使用该方法的区别。只不过我们是从现象上来确认这种情况。
   OK,这些具体的实现方法我会在其具体的实现类里面具体做介绍,这里还是不掺杂那么多的解释或举例。仅仅留给这个ListIterator接口一个说一是一的解析吧。
   至此,我们ListIterator到此全部解析完毕(ಥ_ಥ)。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!