容器(三):ListIterator和Iterator辨析与源码分析
标签: Java编程思想
凡是实现了Collection接口的集合类,都有一个Iterator方法,用于返回一个实现了Iterator接口的对象。但是java集合中还有一个迭代器ListIterator,在使用List、ArrayList、LinkedList和Vector的时候可以使用。这两种迭代器有什么区别呢?下面我们详细分析。
游标
迭代器 没有当前所在元素一说,它只有一个游标(cursor)的概念,这个游标总是在元素之间,比如这样:
也就是说长度为 N 的集合会有 N+1 个游标的位置。
Iterator源码分析
关于Iterator在《容器(一):容器双雄之Collection》中介绍过,话不多说,直接上源码:
package java.util;
import java.util.function.Consumer;
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
default关键字
首先,我们看到Iterator接口代码比较简短。但是我们仔细一看,发现里面居然有方法有方法体?
我们知道,在jdk1.8之前,接口只提供了形式,而未提供任何具体实现,并且实现该接口的实现类必须要实现该接口的所有方法。但是在Java 8 中引入了引入了一个新的概念,叫做default方法
default方法是在java8中引入default关键字
,也可称为虚拟扩展方法。是指: 在接口内部包含了一些默认的方法实现 (也就是接口中可以包含方法体,这打破了Java之前版本对接口的语法限制),从而使得接口在进行扩展的时候,不会破坏与接口相关的实现类代码。
方法分析
Iterator,主要方法有四种:
hasNext()
:迭代器中是否还有下一个元素,若有则返回true。next()
:返回迭代器中的下一个元素,并移动游标。remove()
:删除迭代器新返回的元素。forEachRemaining(Consumer<? super E> + action)
:使用Lambda表达式进行遍历(现在我也不知道啥是Lambda,Java 8 的新特性,以后再说吧),为每个剩余元素执行给定的操作,直到所有的元素都已经被处理或行动将抛出一个异常
使用迭代器next()
方法获得的元素是一个集合中对应元素的深拷贝,如果对迭代变量进行修改是不会修改集合中的原数据的。
同样,也不能直接在迭代过程中使用Collection接口
中的remove()
等方法对集合进行修改,因为迭代器已经锁定住集合了,强行修改会抛出异常!只能用Iterator接口
的专用修改集合元素的方法修改才是正确的,就像上面的Iterator.remove()
方法:
public class Test {
public static void main(String[] args) {
Collection collection = new ArrayList(); // ArrayList是Collection的一个实现类,默认元素类型为Object
Iterator it = collection.iterator();
while (it.hasNext()) {
Type t = it.next(); // 迭代值(数据视图)
对t进行操作;
collection.remove();
// 错误!!在迭代过程中使用非迭代器方法对集合进行修改会直接抛出异常!!
}
}
}
可以看到,Iterator迭代的“集合”是真正集合的视图,视图和真实数据之间是一一映射的关系,如果此时使用Collection接口中的方法相当于对真实数据进行修改,会导致真实数据和映像之间不一致,因此会抛出异常。
Iterator中的修改方法可以保证这种映射的一致性,即迭代器先对视图进行修改,然后将视图的修改更新到真实数据,但是反向就是无效的,因为映像自己是知道关联的是哪个真实数据,但是真实数据本身不知道有哪些映像和我关联的,即真实数据永远是被动的,而映像是主动的!
ListIterator介绍
列表迭代器,允许按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。
ListIterator 没有当前元素;它的游标位置始终位两个元素之间。长度为 n 的列表的迭代器有 n+1 个可能的指针位置,如下面的插入符举例说明:
注意,remove()
和 set(Object)
方法不是根据游标位置定义的;它们是根据对调用 next()
或 previous()
所返回的最后一个元素的操作定义的。
ListIterator源码分析
ListIterator 继承自 Iterator 接口,在 Iterator 的基础上增加了一些内容,话不多说上源码:
package java.util;
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
方法分析
介绍一下新来的几个方法:
oid hasPrevious()
:判断游标是否有前一个元素;Object previous()
:返回游标前面的元素,同时游标前移一位。游标前没有元素就报 java.util.NoSuchElementException 的错,所以使用前最好判断一下;int nextIndex()
:返回游标后边元素的索引位置,初始为 0 ;遍历 N 个元素结束时为 N;int previousIndex()
: 返回游标前面元素的位置,初始时为 -1,同时报 java.util.NoSuchElementException 错;void add(E)
:在游标 前面 插入一个元素
注意,是前面;void set(E)
:更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。
注意,当没有迭代,也就是没有调用 next() 或者 previous() 直接调用 set 时会报 java.lang.IllegalStateException 错;void remove()
: 删除迭代器最后一次操作的元素,注意事项和 set 一样。
ListIterator 的实现
在java.util
包和 java.util.concurrent
包下使用。其具体具体的实现也是在其子接口下完成。
返回 ListIterator 的 java.util 中的方法:
ListIterator<E> AbstractList.listIterator()
:返回此列表元素的列表迭代器(按适当顺序)。ListIterator<E> List.listIterator()
:返回此列表元素的列表迭代器(按适当顺序)。ListIterator<E> LinkedList.listIterator(int index)
:返回此列表中的元素的列表迭代器(按适当顺序),从列表中指定位置开始。abstract ListIterator<E> AbstractSequentialList.listIterator(int index)
:返回在此列表中的元素上进行迭代的列表迭代器(按适当顺序)。ListIterator<E> AbstractList.listIterator(int index)
:返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。ListIterator<E> List.listIterator(int index)
:返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。
返回 ListIterator 的 java.util.concurrent 中的方法:
ListIterator<E> CopyOnWriteArrayList.listIterator()
:返回此列表元素的列表迭代器(按适当顺序)。ListIterator<E> CopyOnWriteArrayList.listIterator(int index)
:返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始。
ListIterator 的代码测试
package char16;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* Created by japson on 8/2/2017.
*/
public class ListIteratorTest {
public static void main(String[] args) {
List<String> lists = new ArrayList<String>();
lists.add("1A");
lists.add("2B");
lists.add("3C");
lists.add("4D");
lists.add("5F");
lists.add("6F");
lists.add("7G");
ListIterator<String> iterator = lists.listIterator();
System.out.println("向后遍历");
//向后遍历
while(iterator.hasNext()){
System.out.print(iterator.next()+" ");
}
System.out.println();
System.out.println("此时游标iterator指向最后一个元素的后面,因此最后一个元素为游标的前置位:" + iterator.previous());
System.out.println();
//此时游标指向7G和6G之间,可以向前遍历
System.out.println("iterator.previous()使得游标前进一位指向7G和6F之间,向前遍历");
while(iterator.hasPrevious()){
System.out.print(iterator.previous()+" ");
}
System.out.println();
System.out.println();
System.out.println("从指定位置(下标2)向前遍历");
//从指定位置向前遍历
iterator = lists.listIterator(2);
while(iterator.hasPrevious()){
System.out.print(iterator.previous()+" ");
}
System.out.println();
System.out.println();
iterator = lists.listIterator();
//替换元素,如何一边替换一边输出?
while(iterator.hasNext()){
String str = iterator.next();
iterator.set("H:"+str); //void方法,不能赋值,也没法输出
System.out.print("替换" + " ");
}
System.out.println();
System.out.println();
System.out.println("通过替换,游标已经移到队尾了,倒序输出");
while(iterator.hasPrevious()){
System.out.print(iterator.previous()+" ");
}
}
}
输出:
向后遍历
1A 2B 3C 4D 5F 6F 7G
此时游标iterator指向最后一个元素的后面,因此最后一个元素为游标的前置位:7G
iterator.previous()使得游标前进一位指向7G和6F之间,向前遍历
6F 5F 4D 3C 2B 1A
从指定位置(下标2)向前遍历
2B 1A
替换 替换 替换 替换 替换 替换 替换
通过替换,游标已经移到队尾了,倒序输出
H:7G H:6F H:5F H:4D H:3C H:2B H:1A
ListIterator和Iterator的相同点
都是迭代器,当需要对集合中元素进行遍历不需要干涉其遍历过程时,这两种迭代器都可以使用。
ListIterator和Iterator的不同点
使用范围不同,Iterator可以应用于所有的Collection的子类型。而ListIterator只能用于List及其子类型。
ListIterator有add方法,可以向List中添加对象,而Iterator不能。
ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。
ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。
ps:用心学习,喜欢的话请点赞 (在左侧哦)