Java 迭代接口:Iterator、ListIterator 和 Spliterator
1. 简介
当我们使用 for
或 while
循环来遍历一个集合的元素,Iterator
允许我们不用担心索引位置,甚至让我们不仅仅是遍历一个集合,同时还可以改变它。例如,你如果要删除循环中的元素,那么 for
循环不见得总是可行的。
结合自定义的迭代器,我们可以迭代更为复杂的对象,以及向前和向后移动,并且知晓如何利用其优势也将变得非常清楚。
本文将深入讨论如何使用 Iterator
和 Iterable
接口。
2. Iterator()
Iterator
接口用于迭代集合中的元素(List
,Set
或 Map
)。它用于逐个检索元素,并在需要时针对每个元素执行操作。
下面是用于遍历集合与执行操作的方法:
.hasNext()
:如果还没有到达集合的末尾,则返回true
,否则返回false
.next()
:返回集合中的下一个元素.remove()
:从集合中移除迭代器返回的最后一个元素.forEachRemaining()
:按顺序为集合中剩下的每个元素执行给定的操作
首先,由于迭代器是用于集合的,让我们做一个简单的包含几个元素的 ArrayList
:
List<String> avengers = new ArrayList<>();
// Now lets add some Avengers to the list
avengers.add("Ant-Man");
avengers.add("Black Widow");
avengers.add("Captain America");
avengers.add("Doctor Strange");
我们可以使用一个简单循环来遍历这个集合:
System.out.println("Simple loop example:\n");
for (int i = 0; i < avengers.size(); i++) {
System.out.println(avengers.get(i));
}
不过,我们想探索迭代器:
System.out.println("\nIterator Example:\n");
// First we make an Iterator by calling
// the .iterator() method on the collection
Iterator<String> avengersIterator = avengers.iterator();
// And now we use .hasNext() and .next() to go through it
while (avengersIterator.hasNext()) {
System.out.println(avengersIterator.next());
}
如果我们想从这个 ArrayList
中删除一个元素,会发生什么?让我们试着使用常规的 for
循环:
System.out.println("Simple loop example:\n");
for (int i = 0; i < avengers.size(); i++) {
if (avengers.get(i).equals("Doctor Strange")) {
avengers.remove(i);
}
System.out.println(avengers.get(i));
}
我们会收到一个讨厌的 IndexOutOfBoundsException
:
Simple loop example:
Ant-Man
Black Widow
Captain America
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
这在遍历集合时更改其大小是有意义的,增强 for
循环也一样:
System.out.println("Simple loop example:\n");
for (String avenger : avengers) {
if (avenger.equals("Doctor Strange")) {
avengers.remove(avenger);
}
System.out.println(avenger);
}
我们再次收到了另一个异常:
Simple loop example:
Ant-Man
Black Widow
Captain America
Doctor Strange
Exception in thread "main" java.util.ConcurrentModificationException
这时迭代器就派上用场了,由它充当中间人,从集合中删除元素,同时确保遍历按计划继续:
Iterator<String> avengersIterator = avengers.iterator();
while (avengersIterator.hasNext()) {
String avenger = avengersIterator.next();
// First we must find the element we wish to remove
if (avenger.equals("Ant-Man")) {
// This will remove "Ant-Man" from the original
// collection, in this case a List
avengersIterator.remove();
}
}
这是保证在遍历集合时删除元素的安全方法。
并确认该元素是否已被删除:
// We can also use the helper method .forEachRemaining()
System.out.println("For Each Remaining Example:\n");
Iterator<String> avengersIteratorForEach = avengers.iterator();
// This will apply System.out::println to all elements in the collection
avengersIteratorForEach.forEachRemaining(System.out::println);
输出如下:
For Each Remaining Example:
Black Widow
Captain America
Doctor Strange
正如你所看到的,蚁人已经从 复仇者联盟
的名单中删除了。
2.1. ListIterator()
ListIterator
继承自 Iterator
接口。它只在 List
上进行使用,可以双向迭代,这意味着你可以从前到后或从后到前进行迭代。它也没有 current 元素,因为游标总是放在 List
的两个元素之间,所以我们用 .previous()
或 .next()
来访问元素。
Iterator
和ListIterator
之间有什么区别呢?
首先,Iterator
可以用于 任意集合 —— List
、Map
、Queue
、Set
等。
ListIterator
只能应用于 List,通过添加这个限制,ListIterator
在方法方面可以更加具体,因此,我们引入了许多新方法,他们可以帮助我们在遍历时对其进行修改。
如果你正在处理 List
实现(ArrayList
、LinkedList
等),那么使用 ListIterator
更为可取一些。
下面是你可能会用到的方法:
.add(E e)
:向 List 中添加元素。.remove()
:从 List 中删除.next()
或.previous()
返回的最后一个元素。.set(E e)
:使用指定元素来覆盖 List.next()
或.previous()
返回的最后一个元素。.hasNext()
:如果还没有到达 List 的末尾,则返回true
,否则返回false
。.next()
:返回 List 中的下一个元素。.nextIndex()
:返回下一元素的下标。.hasPrevious()
:如果还没有到达 List 的开头,则返回true
,否则返回false
。.previous()
:返回 List 的上一个元素。.previousIndex()
:返回上一元素的下标。
再次,让我们用一些元素构成一个 ArrayList
:
ArrayList<String> defenders = new ArrayList<>();
defenders.add("Daredevil");
defenders.add("Luke Cage");
defenders.add("Jessica Jones");
defenders.add("Iron Fist");
让我们用 ListIterator
来遍历 List 并打印其元素:
ListIterator listIterator = defenders.listIterator();
System.out.println("Original contents of our List:\n");
while (listIterator.hasNext())
System.out.print(listIterator.next() + System.lineSeparator());
显然,它的工作方式与 Iterator
相同。输出如下:
Original contents of our List:
Daredevil
Luke Cage
Jessica Jones
Iron Fist
现在,让我们来尝试修改一些元素:
System.out.println("Modified contents of our List:\n");
// Now let's make a ListIterator and modify the elements
ListIterator defendersListIterator = defenders.listIterator();
while (defendersListIterator.hasNext()) {
Object element = defendersListIterator.next();
defendersListIterator.set("The Mighty Defender: " + element);
}
现在打印 List 的话会得到如下结果:
Modified contents of our List:
The Mighty Defender: Daredevil
The Mighty Defender: Luke Cage
The Mighty Defender: Jessica Jones
The Mighty Defender: Iron Fist
现在,让我们倒着遍历列表,就像我们可以用 ListIterator
做的那样:
System.out.println("Modified List backwards:\n");
while (defendersListIterator.hasPrevious()) {
System.out.println(defendersListIterator.previous());
}
输出如下:
Modified List backwards:
The Mighty Defender: Iron Fist
The Mighty Defender: Jessica Jones
The Mighty Defender: Luke Cage
The Mighty Defender: Daredevil
3. Spliterator()
Spliterator
接口在功能上与 Iterator
相同。你可能永远不需要直接使用 Spliterator
,但让我们继续讨论一些用例。
但是,你应首先熟悉 Java Streams 和 Lambda Expressions in Java。
虽然我们将列出 Spliterator
拥有的所有方法,但是 Spliterator
接口的全部工作超出了本文的范畴。我们将通过一个例子讨论 Spliterator
如何使用并行化更有效地遍历我们可以分解的 Stream
。
我们在处理 Spliterator
时使用的方法是:
.characteristics()
: 返回该 Spliterator 具有的作为
int
值的特征。 这些包括:
ORDERED
DISTINCT
SORTED
SIZED
CONCURRENT
IMMUTABLE
NONNULL
SUBSIZED
.estimateSize()
:返回遍历作为long
值遇到的元素数量的估计值,如果无法返回则返回long.MAX_VALUE
。.forEachRemaining(E e)
:按顺序对集合中的每个剩余元素执行给定操作。.getComparator()
:如果该Spliterator
的源是由Comparator
排序的,其将返回Comparator
。.getExactSizeIfKnown()
:如果大小已知则返回.estimateSize()
,否则返回-1
。.hasCharacteristics(int characteristics)
:如果这个Spliterator
的.characteristics()
包含所有给定的特征,则返回true
。.tryAdvance(E e)
:如果存在剩余元素,则对其执行给定操作,返回true
,否则返回false
。.trySplit()
:如果这个Spliterator
可以被分区,返回一个Spliterator
覆盖元素,当从这个方法返回时,它将不被这个Spliterator
覆盖。
像往常一样,让我们从一个简单的 ArrayList
开始:
List<String> mutants = new ArrayList<>();
mutants.add("Professor X");
mutants.add("Magneto");
mutants.add("Storm");
mutants.add("Jean Grey");
mutants.add("Wolverine");
mutants.add("Mystique");
现在,我们需要将 Spliterator
应用于 Stream
。值得庆幸的是,由于 Collections 框架,很容易在 ArrayList
和 Stream
之间进行转换:
// Obtain a Stream to the mutants List.
Stream<String> mutantStream = mutants.stream();
// Getting Spliterator object on mutantStream.
Spliterator<String> mutantList = mutantStream.spliterator();
为了展示其中的一些方法,让我们分别运行下它们:
// .estimateSize() method
System.out.println("Estimate size: " + mutantList.estimateSize());
// .getExactSizeIfKnown() method
System.out.println("\nExact size: " + mutantList.getExactSizeIfKnown());
System.out.println("\nContent of List:");
// .forEachRemaining() method
mutantList.forEachRemaining((n) -> System.out.println(n));
// Obtaining another Stream to the mutant List.
Spliterator<String> splitList1 = mutantStream.spliterator();
// .trySplit() method
Spliterator<String> splitList2 = splitList1.trySplit();
// If splitList1 could be split, use splitList2 first.
if (splitList2 != null) {
System.out.println("\nOutput from splitList2:");
splitList2.forEachRemaining((n) -> System.out.println(n));
}
// Now, use the splitList1
System.out.println("\nOutput from splitList1:");
splitList1.forEachRemaining((n) -> System.out.println(n));
我们将得到输出:
Estimate size: 6
Exact size: 6
Content of List:
Professor X
Magneto
Storm
Jean Grey
Wolverine
Mystique
Output from splitList2:
Professor X
Magneto
Storm
Output from splitList1:
Jean Grey
Wolverine
Mystique
4. Iterable()
如果出于某种原因,我们想要创建一个自定义的 Iterator
接口,应该怎么办?你首先要熟悉的是这张图:
要创建自定义 Iterator
,我们需要为 .hasNext()
、.next()
和 .remove()
做自定义实现。
在 Iterator
接口中,有一个方法,它返回一个集合中元素的迭代器,即 .iterator()
方法,还有一个方法为迭代器中的每个元素执行一个操作的方法,即 .dorEach()
方法。
例如,假设我们是 Tony Stark,我们需要写个自定义迭代器来列出当前武器库中的每件钢铁侠套装。
首先,让我们创建一个类来获取和设置 suit 数据:
public class Suit {
private String codename;
private int mark;
public Suit(String codename, int mark) {
this.codename = codename;
this.mark = mark;
}
public String getCodename() { return codename; }
public int getMark() { return mark; }
public void setCodename (String codename) {this.codename=codename;}
public void setMark (int mark) {this.mark=mark;}
public String toString() {
return "mark: " + mark + ", codename: " + codename;
}
}
接下来让我们编写自定义 Iterator:
// Our custom Iterator must implement the Iterable interface
public class Armoury implements Iterable<Suit> {
// Notice that we are using our own class as a data type
private List<Suit> list = null;
public Armoury() {
// Fill the List with data
list = new LinkedList<Suit>();
list.add(new Suit("HOTROD", 22));
list.add(new Suit("SILVER CENTURION", 33));
list.add(new Suit("SOUTHPAW", 34));
list.add(new Suit("HULKBUSTER 2.0", 48));
}
public Iterator<Suit> iterator() {
return new CustomIterator<Suit>(list);
}
// Here we are writing our custom Iterator
// Notice the generic class E since we do not need to specify an exact class
public class CustomIterator<E> implements Iterator<E> {
// We need an index to know if we have reached the end of the collection
int indexPosition = 0;
// We will iterate through the collection as a List
List<E> internalList;
public CustomIterator(List<E> internalList) {
this.internalList = internalList;
}
// Since java indexes elements from 0, we need to check against indexPosition +1
// to see if we have reached the end of the collection
public boolean hasNext() {
if (internalList.size() >= indexPosition +1) {
return true;
}
return false;
}
// This is our custom .next() method
public E next() {
E val = internalList.get(indexPosition);
// If for example, we were to put here "indexPosition +=2" we would skip every
// second element in a collection. This is a simple example but we could
// write very complex code here to filter precisely which elements are
// returned.
// Something which would be much more tedious to do with a for or while loop
indexPosition += 1;
return val;
}
// In this example we do not need a .remove() method, but it can also be
// written if required
}
}
最后是 main 方法类:
public class IronMan {
public static void main(String[] args) {
Armoury armoury = new Armoury();
// Instead of manually writing .hasNext() and .next() methods to iterate through
// our collection we can simply use the advanced forloop
for (Suit s : armoury) {
System.out.println(s);
}
}
}
输出如下:
mark: 22, codename: HOTROD
mark: 33, codename: SILVER CENTURION
mark: 34, codename: SOUTHPAW
mark: 48, codename: HULKBUSTER 2.0
5. 总结
本文中,我们详细讨论了如何使用 Java 中的迭代器,甚至写了一个定制的迭代器来探索 Iterable
接口的所有新的可能性。
我们还讨论了 Java 是如何利用 Stream 的并行化,使用 Spliterator
接口对集合的遍历进行内部优化。
8月福利准时来袭,关注公众号
后台回复:003即可领取7月翻译集锦哦~
往期福利回复:001,002即可领取!
Java 迭代接口:Iterator、ListIterator 和 Spliterator的更多相关文章
- Java容器类接口:Iterator,Collection,Map
Iterator Iterator被称为迭代器,是一个对象,它的工作是遍历并选择序列中的对象,可以实现以下一些操作: 使用方法iterator()要求容器返回一个Iterator,Iterator将返 ...
- java:集合输出Iterator,ListIterator,foreach,Enumeration
//集合输出,集合的四种输出 Iterator, ListIterator, foreach, Enumeration 只要碰到集合,第一输出选择是Iterator类. Iterator<E&g ...
- 【转】Java迭代:Iterator和Iterable接口
Java迭代 : Iterator和Iterable接口 从英文意思去理解 Iterable :故名思议,实现了这个接口的集合对象支持迭代,是可迭代的.able结尾的表示 能...样,可以做.... ...
- 设计模式 - 迭代模式(iterator pattern) Java 迭代器(Iterator) 详细解释
迭代模式(iterator pattern) Java 迭代器(Iterator) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 參考迭代器模式(ite ...
- Iterator——迭代接口
迭代对于JAVA的来说绝对不陌生.我们常常使用JDK提供的迭代接口进行Java集合的迭代. Iterator iterator = list.iterator(); while(iterator.ha ...
- 源码阅读—Iterator接口和LIstIterator接口
在继续看ArrayList源码之前,先了解Iterator接口和ListIterator接口,下篇文章详细讲解ArrayList是如何实现它们的. 我们知道,接口只是一种规范,当继承接口并实现其中的方 ...
- java中的Iterator接口
Iterator接口 Iterator接口也是Java集合框架的成员,但它与Collection系列.Map系列的集合不一样:Collection系列集合.Map系列集合主要用于盛装其他对象,而Ite ...
- Java集合框架之接口Iterator
简述 Iterator迭代器的定义:迭代器(Iterator)模式,又叫做游标(Cursor)模式.GOF给出的定义是,提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象 ...
- Java Iterator, ListIterator 和 foreach语句使用
Java Iterator, ListIterator 和 foreach语句使用 foreach语句结构: for(part1:part2){part3}; part2 中是一个数组对象,或者是带 ...
随机推荐
- java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能
这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能 ...
- [小米OJ] 9. 移除 K 位得到最小值
思路: 重复k次: 1.找到并且删除第一个 num[i] > num[i+1] 的第i位数字. 2.若删除过程中,序列变成递增序列,则直接删除最后一位. 注意除去字符串头的0 def solut ...
- [leetcode] 111.Mininum Depth of Binary Tree (Easy)
原题 寻找二叉树最短深度 这里用了dfs,beat 100%,4ms class Solution { public: int minDepth(TreeNode *root, int minNum ...
- sql nvarchar类型和varchar类型存储中文字符长度
今天遇到了,随手记录一下. sql server 存储数据里面 NVARCHAR 记录中文的时候是 一个中文对应一个字符串长度,记录英文也是一个字母一个长度 标点符号也是一样. ...
- vue系列---vue项目(已安装vuex)中引入jquery
vue项目中引入jquery有很多方法,这只是其中一种. 步骤如下: 1,安装jquery依赖 npm install jquery --save 如果是使用淘宝镜像则将npm改为cnpm 2,修改配 ...
- JQuery操作CheckBox 第二次无法选中的问题
用JQuery做CheckBox全选和反选的时候,遇到一个问题.当用JQ控制全选,全取消一次以后,再次点击全选,发现代码变了,但是CheckBox没有处于选中状态. 百度后得知: 我使用的方法是 $( ...
- golang在多个go routine中进行map或者slice操作应该注意的对象。
因为golang的map和列表切片都是引用类型,且非线程安全的,所以在多个go routine中进行读写操作的时候,会产生“map read and map write“的panic错误. 某一些类型 ...
- python交互界面无法使用方向键
问题 python交互界面无法使用方向键,按方向键全变成^[[^C这类型的字符 解决办法 办法1: 使用yum安装readline.readline-devel,然后重装python 这种方法太麻烦了 ...
- 【Sublime】设置显示编码格式
Mac 上的 Sublime 显示编码格式,设置方法: 右下角显示的 UTF-8 就是当前的编码格式. 添加如下代码: { "font_size": 18, // Display ...
- HelloDjango 系列教程:第 04 篇:Django 迁移、操作数据库
文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 我们已经编写了博客数据库模型的代码,但那还只是 Python 代码而已,django 还没有把它翻译成数据库语言,因此实际上这 ...