在Java中,我们能否编写自己的迭代器?

113
如果我有一个包含 [alice, bob, abigail, charlie] 的列表,并且我想编写一个迭代器,使其遍历以'a'开头的元素,我可以自己编写吗?我该如何做?

4
可以的。您需要实现Iterator接口。 - gd1
当然,这只是一个普通的接口。为JDO实现代理java.util涉及相当多的自定义迭代器。 - bestsss
http://codereview.stackexchange.com/questions/48109/simple-example-of-an-iterable-and-an-iterator-in-java - Ciro Santilli OurBigBook.com
6个回答

213

最佳的可重复使用选项是实现Iterable接口并覆盖iterator()方法。

这里有一个类似ArrayList的类实现了该接口,其中覆盖了Iterator()方法。

import java.util.Iterator;

public class SOList<Type> implements Iterable<Type> {

    private Type[] arrayList;
    private int currentSize;

    public SOList(Type[] newArray) {
        this.arrayList = newArray;
        this.currentSize = arrayList.length;
    }

    @Override
    public Iterator<Type> iterator() {
        Iterator<Type> it = new Iterator<Type>() {

            private int currentIndex = 0;

            @Override
            public boolean hasNext() {
                return currentIndex < currentSize && arrayList[currentIndex] != null;
            }

            @Override
            public Type next() {
                return arrayList[currentIndex++];
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
        return it;
    }
}

这个类使用泛型实现了Iterable接口。如果你有一个数组元素,你就可以得到一个Iterator的实例,这是“foreach”循环所需的实例。

你可以创建一个匿名迭代器实例而不用扩展Iterator,并利用currentSize的值来验证你可以遍历数组到哪里(假设你创建了一个容量为10但只有0和1索引处有2个元素的数组)。该实例将拥有它自己的计数器来记录它所处的位置,你需要做的就是使用hasNext()方法进行判断当前值是否为空,和使用next()方法来返回您当前索引的实例。下面是使用这个API的示例...

public static void main(String[] args) {
    // create an array of type Integer
    Integer[] numbers = new Integer[]{1, 2, 3, 4, 5};

    // create your list and hold the values.
    SOList<Integer> stackOverflowList = new SOList<Integer>(numbers);

    // Since our class SOList is an instance of Iterable, then we can use it on a foreach loop
    for(Integer num : stackOverflowList) {
        System.out.print(num);
    }

    // creating an array of Strings
    String[] languages = new String[]{"C", "C++", "Java", "Python", "Scala"};

    // create your list and hold the values using the same list implementation.
    SOList<String> languagesList = new SOList<String>(languages);

    System.out.println("");
    // Since our class SOList is an instance of Iterable, then we can use it on a foreach loop
    for(String lang : languagesList) {
        System.out.println(lang);
    }
}
// will print "12345
//C
//C++
//Java
//Python
//Scala

如果你想的话,你也可以使用Iterator实例迭代它:

// navigating the iterator
while (allNumbers.hasNext()) {
    Integer value = allNumbers.next();
    if (allNumbers.hasNext()) {
        System.out.print(value + ", ");
    } else {
        System.out.print(value);
    }
} 
// will print 1, 2, 3, 4, 5

foreach的文档位于http://download.oracle.com/javase/1,5.0/docs/guide/language/foreach.html。您可以在我的个人实践Google code中查看更完整的实现。

现在,为了获得所需的效果,我认为您需要在迭代器中插入一个过滤器的概念...由于迭代器依赖于下一个值,因此很难在hasNext()上返回true,然后使用不以字符"a"开头的值来过滤next()实现。我认为您需要使用基于已过滤列表的辅助迭代器,并使用给定过滤器的值。


18
“for instance”这个词组是否属于双关语? - n611x007
4
30个人不认为那是一个双关语 :) - Marcello DeSales
3
在我们实现的方法中抛出不支持操作异常是一个好的实践。我认为从 remove() 方法抛出不支持操作异常是个好主意! - darshan
2
抱歉@darshan,但这个解决方案是关于“如何编写迭代器”...如果重点是“编写完美的代码”,那就不在这里了! - Marcello DeSales
1
不清楚为什么在hasNext()中需要检查'arrayList[currentIndex] != null'。有人可以解释一下吗? - Bhushan Karmarkar

51

当然可以。迭代器只是 java.util.Iterator 接口的一种实现。如果你使用一个现有的可迭代对象(比如 LinkedList)来自于java.util,你需要继承这个类,并重写它的 iterator 函数,使其返回你自己的迭代器;或者提供一种包装标准迭代器的方法,将其封装在你的特殊 Iterator 实例中(这样做的优点是可以更广泛地使用),等等。


9
好的回答!+1。然而,您并不需要强制子类化 LinkedList。您可以编写一个 CustomIterator,并使用 new CustomIterator(somelist) 实例化它,因为接口对构造函数一无所知。 - gd1
1
@Giacomo:这就是我所说的“……或者提供一种方法在您的特殊'Iterator'实例中包装标准迭代器……”(谢谢)。 :-) - T.J. Crowder

15

使用Iterable计算阶乘的好例子

FactorialIterable fi = new FactorialIterable(10);
Iterator<Integer> iterator = fi.iterator();
while (iterator.hasNext()){
     System.out.println(iterator.next());
}

Java 1.8的短代码

new FactorialIterable(5).forEach(System.out::println);

自定义迭代器类

public class FactorialIterable implements Iterable<Integer> {

    private final FactorialIterator factorialIterator;

    public FactorialIterable(Integer value) {
        factorialIterator = new FactorialIterator(value);
    }

    @Override
    public Iterator<Integer> iterator() {
        return factorialIterator;
    }

    @Override
    public void forEach(Consumer<? super Integer> action) {
        Objects.requireNonNull(action);
        Integer last = 0;
        for (Integer t : this) {
            last = t;
        }
        action.accept(last);
    }

}

自定义迭代器类

public class FactorialIterator implements Iterator<Integer> {

    private final Integer mNumber;
    private Integer mPosition;
    private Integer mFactorial;


    public FactorialIterator(Integer number) {
        this.mNumber = number;
        this.mPosition = 1;
        this.mFactorial = 1;
    }

    @Override
    public boolean hasNext() {
        return mPosition <= mNumber;
    }

    @Override
    public Integer next() {
        if (!hasNext())
            return 0;

        mFactorial = mFactorial * mPosition;

        mPosition++;

        return  mFactorial;
    }
}

9

下面是完整的代码,用于编写一个迭代器,使其迭代以字母“a”开头的元素:

import java.util.Iterator;

public class AppDemo {

    public static void main(String args[]) {

        Bag<String> bag1 = new Bag<>();

        bag1.add("alice");
        bag1.add("bob"); 
        bag1.add("abigail");
        bag1.add("charlie"); 

        for (Iterator<String> it1 = bag1.iterator(); it1.hasNext();) {

            String s = it1.next();
            if (s != null)
                System.out.println(s); 
        }
    }
}

自定义迭代器类
import java.util.ArrayList;
import java.util.Iterator;

public class Bag<T> {

    private ArrayList<T> data;

    public Bag() {

        data = new ArrayList<>();
    }

    public void add(T e) {

        data.add(e); 
    }

    public Iterator<T> iterator() {

        return new BagIterator();
    }

    public class BagIterator<T> implements Iterator<T> {

        private int index; 
        private String str;

        public BagIterator() {

            index = 0;
        }

        @Override
        public boolean hasNext() {

             return index < data.size();  
        }

        @Override
        public T next() {

            str = (String) data.get(index); 
            if (str.startsWith("a"))
                return (T) data.get(index++); 
            index++; 
            return null; 
        }
    } 
}

5

您可以实现自己的迭代器。您的迭代器可以构造为包装列表返回的迭代器,或者您可以保持光标并使用列表的get(int index)方法。您只需要在迭代器的next方法和hasNext方法中添加逻辑以考虑您的过滤条件。您还必须决定您的迭代器是否支持删除操作。


5

以下是该问题的完整答案。

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

class ListIterator implements Iterator<String>{
    List<String> list;
    int pos = 0;

    public ListIterator(List<String> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        while(pos < list.size()){
            if (list.get(pos).startsWith("a"))
                return true;
            pos++;
        }
        return false;

    }

    @Override
    public String next() {
        if (hasNext())
            return list.get(pos++);
        throw new NoSuchElementException();
    }
}

public class IteratorTest {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("alice", "bob", "abigail", "charlie");
        ListIterator itr = new ListIterator(list);

        while(itr.hasNext())
            System.out.println(itr.next()); // prints alice, abigail
    }
}
  • ListIterator是数组的迭代器,返回以'a'开头的元素。
  • 无需实现Iterable接口,但这是一个可能性。
  • 不需要以泛型方式实现。
  • 它完全满足hasNext()和next()的协议。即如果hasNext()表示仍然有元素,则next()会返回这些元素。如果hasNext()表明没有更多元素,它将返回有效的NoSuchElementException异常。

我认为您的实现失去了迭代器的主要优点,即 hasNext() 和 next() 的常数时间复杂度。 - Ayoub Omari
@Quade。hasNext和next永远不可能同时是常数时间。想象一下一个没有匹配项的列表。必须遍历整个列表才能确定没有匹配项。这怎么可能是常数时间呢? - apadana
很好的回答,实际上,next 应该仍然在常数时间内运行(取决于列表的实现方式)。而且 @Ayoub Omari,在 hasNext 中引入的 while 循环是一个不可避免的计算,必须在某个时候完成以进行过滤位置的移动,因此在 hasNext 方法中完成这个操作与在迭代器外部进行过滤相同。你无论如何都必须编写代码来完成这个操作。 - Lorenzo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接