Java 迭代接口:Iterator、ListIterator 和 Spliterator

1. 简介

当咱们使用 forwhile 循环来遍历一个集合的元素,Iterator 容许咱们不用担忧索引位置,甚至让咱们不只仅是遍历一个集合,同时还能够改变它。例如,你若是要删除循环中的元素,那么 for 循环不见得老是可行的。java

结合自定义的迭代器,咱们能够迭代更为复杂的对象,以及向前和向后移动,而且知晓如何利用其优点也将变得很是清楚。安全

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

2. Iterator()

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

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

  • .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");复制代码

咱们可使用一个简单循环来遍历这个集合:ui

System.out.println("Simple loop example:\n");
for (int i = 0; i < avengers.size(); i++) {
    System.out.println(avengers.get(i));
}复制代码

不过,咱们想探索迭代器:this

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 循环:spa

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 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 mutantStream = mutants.stream();

// Getting Spliterator object on mutantStream.Spliterator mutantList = mutantStream.spliterator();

为了展现其中的一些方法,让咱们分别运行下它们:
复制代码

// .estimateSize() methodSystem.out.println("Estimate size: " + mutantList.estimateSize());

// .getExactSizeIfKnown() methodSystem.out.println("nExact size: " + mutantList.getExactSizeIfKnown());

System.out.println("nContent of List:");// .forEachRemaining() methodmutantList.forEachRemaining((n) -> System.out.println(n));

// Obtaining another Stream to the mutant List.Spliterator splitList1 = mutantStream.spliterator();

// .trySplit() methodSpliterator 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 splitList1System.out.println("nOutput from splitList1:");splitList1.forEachRemaining((n) -> System.out.println(n));

咱们将获得输出:
复制代码

Estimate size: 6

Exact size: 6

Content of List:Professor XMagnetoStormJean GreyWolverineMystique

Output from splitList2:Professor XMagnetoStorm

Output from splitList1:Jean GreyWolverineMystique

## 4. Iterable()

若是出于某种缘由,咱们想要建立一个自定义的 `Iterator` 接口,应该怎么办?你首先要熟悉的是这张图:

![file](https://user-gold-cdn.xitu.io/2019/8/27/16cd06ab2be585b5?w=800&h=800&f=jpeg&s=54890)

要建立自定义 `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便可领取!

img

相关文章
相关标签/搜索