1. 简介

当我们使用 forwhile 循环来遍历一个集合的元素,Iterator 允许我们不用担心索引位置,甚至让我们不仅仅是遍历一个集合,同时还可以改变它。例如,你如果要删除循环中的元素,那么 for 循环不见得总是可行的。

结合自定义的迭代器,我们可以迭代更为复杂的对象,以及向前和向后移动,并且知晓如何利用其优势也将变得非常清楚。

本文将深入讨论如何使用 IteratorIterable 接口。

2. Iterator()

Iterator 接口用于迭代集合中的元素(ListSetMap)。它用于逐个检索元素,并在需要时针对每个元素执行操作。

下面是用于遍历集合与执行操作的方法:

  • .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() 来访问元素。

IteratorListIterator 之间有什么区别呢?

首先,Iterator 可以用于 任意集合 —— ListMapQueueSet 等。

ListIterator 只能应用于 List,通过添加这个限制,ListIterator 在方法方面可以更加具体,因此,我们引入了许多新方法,他们可以帮助我们在遍历时对其进行修改。

如果你正在处理 List 实现(ArrayListLinkedList等),那么使用 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 StreamsLambda 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 框架,很容易在 ArrayListStream 之间进行转换:

// 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的更多相关文章

  1. Java容器类接口:Iterator,Collection,Map

    Iterator Iterator被称为迭代器,是一个对象,它的工作是遍历并选择序列中的对象,可以实现以下一些操作: 使用方法iterator()要求容器返回一个Iterator,Iterator将返 ...

  2. java:集合输出Iterator,ListIterator,foreach,Enumeration

    //集合输出,集合的四种输出 Iterator, ListIterator, foreach, Enumeration 只要碰到集合,第一输出选择是Iterator类. Iterator<E&g ...

  3. 【转】Java迭代:Iterator和Iterable接口

    Java迭代 : Iterator和Iterable接口 从英文意思去理解 Iterable :故名思议,实现了这个接口的集合对象支持迭代,是可迭代的.able结尾的表示 能...样,可以做.... ...

  4. 设计模式 - 迭代模式(iterator pattern) Java 迭代器(Iterator) 详细解释

    迭代模式(iterator pattern) Java 迭代器(Iterator) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 參考迭代器模式(ite ...

  5. Iterator——迭代接口

    迭代对于JAVA的来说绝对不陌生.我们常常使用JDK提供的迭代接口进行Java集合的迭代. Iterator iterator = list.iterator(); while(iterator.ha ...

  6. 源码阅读—Iterator接口和LIstIterator接口

    在继续看ArrayList源码之前,先了解Iterator接口和ListIterator接口,下篇文章详细讲解ArrayList是如何实现它们的. 我们知道,接口只是一种规范,当继承接口并实现其中的方 ...

  7. java中的Iterator接口

    Iterator接口 Iterator接口也是Java集合框架的成员,但它与Collection系列.Map系列的集合不一样:Collection系列集合.Map系列集合主要用于盛装其他对象,而Ite ...

  8. Java集合框架之接口Iterator

    简述 Iterator迭代器的定义:迭代器(Iterator)模式,又叫做游标(Cursor)模式.GOF给出的定义是,提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象 ...

  9. Java Iterator, ListIterator 和 foreach语句使用

    Java Iterator, ListIterator 和 foreach语句使用 foreach语句结构: for(part1:part2){part3};  part2 中是一个数组对象,或者是带 ...

随机推荐

  1. 「PowerBI相关」一款极其优秀的DAX建模工具Tabular Editor

    做Excel插件开发的时间久了,大部分人的印象是笔者是做软件开发,谈不上软件,也是插件开发级别. 但笔者更希望大家记住的身份是做BI开发,特别是企业级的BI. 故也借着Excel催化剂这个平台,输送一 ...

  2. idea 警告:Warning:java: 源值1.5已过时, 将在未来所有发行版中删除

    在pom.xml文件中添加 <properties>         <maven.compiler.source>1.8</maven.compiler.source& ...

  3. docker实战(一)之Tomcat的安装

           docker号称分分钟就可以将环境构建完成,这话一点也不假,因为docker在使用软件时只需要从官方 仓库中拉取对应的镜像就行了.docker的使用前需要了解两个名词--镜像和容器.这两 ...

  4. 重复造轮子系列——基于FastReport设计打印模板实现桌面端WPF套打和商超POS高度自适应小票打印

    重复造轮子系列——基于FastReport设计打印模板实现桌面端WPF套打和商超POS高度自适应小票打印 一.引言 桌面端系统经常需要对接各种硬件设备,比如扫描器.读卡器.打印机等. 这里介绍下桌面端 ...

  5. VIM常用命令速查(转)

  6. 优雅的对象转换解决方案-MapStruct使用进阶(二)

    在前面, 介绍了 MapStruct 及其入门. 本文则是进一步的进阶. 在 MapStruct 生成对应的实现类的时候, 有如下的几个情景. 1 属性名称相同,则进行转化 在实现类的时候, 如果属性 ...

  7. mysql 查询结果显示行号

    mysql 查询时,不像oracle那样,可以直接用 rownum 显示结果行号. 可以用定义用户变量来实现 set @myrnum = 0; select (@myrnum := @myrnum + ...

  8. Hyper-v设置linux固定ip

    一.创建CentOS 7专用的虚拟交换机 打开Hyper-v控制面板,找到右边的“虚拟交换机管理器” 进去后,点击“新建虚拟网络交换机”,填写名称后,选择“内部” 打开网络中心,修改配置如下图,注意i ...

  9. 消息中间件-activemq实战整合Spring之Topic模式(五)

    这一节我们看一下Topic模式下的消息发布是如何处理的. applicationContext-ActiveMQ.xml配置: <?xml version="1.0" enc ...

  10. STM32CubeMX工程修改MCU的两种方法

    有些时候我们在已经使用过一段时间的stm32cube创建的工程,需要更换一个同系列的芯片,比如Flash空间更大或者更小,第一种方法我在网上搜索过,就是使用cube选择一个新使用型号的MCU,然后使用 ...