1

为什么在迭代器引发 NoSuchElementException 并停止循环执行时不考虑 foreach 循环?

让我解释。

通常迭代器需要提供两个重要的方法:hasNext 和 next。如果没有更多元素要迭代,则下一个方法可以引发 NoSuchElementException。hasNext 方法更像是一种语法糖,只是为了让你的代码更好一点。重要的方法实际上是 next ,这是应该做任何工作的方法,如果有的话,但不是 hasNext ,它需要在没有预先调用 hasNext 的假设的情况下实现。

假设我们有以下情况:

final Iterator<String> bogusIt01 = new Iterator<String>() {

    public boolean hasNext() {
        return true;
    }

    public String next() {
        throw new NoSuchElementException("no such element");
    }

    public void remove() {
        throw new UnsupportedOperationException("cannot remove element");
    }

};

final Iterable<String> bogusPr01 = new Iterable<String>() {

    public Iterator<String> iterator() {
        return bogusIt01;
    }

};

for (@SuppressWarnings("unused") String string:bogusPr01) {
    // pass
}

这将引发 NoSuchElementException。我希望循环只是终止。我做错了吗?

我问的原因是因为使用这个模型很难编写一个迭代器来包裹另一个迭代器并对其输出进行一些处理。让我们想象以下场景:

Iterator1 <- 这是我们原来的迭代器,一切都很好。

Iterator2 <- 此迭代器环绕 Iterator1 并修改迭代元素的输出。

Iterator2 是这样实现的:

    public boolean hasNext() {
        return iterator1.hasNext();
    }

    public Object next() {
        if (!iterator1.hasNext()) {
            throw new NoSuchElementException("no such element");
        }

        Object nextValue = iterator1.next();

        try {
            // do something that could raise exception
        } catch (Exception e) {
            return this.next();
        }

        return productValue;
    }

    public void remove() {
        throw new UnsupportedOperationException("cannot remove element");
    }

这样的设计根本不可能,尽管它对我来说很有意义。

有一个更好的方法吗?

4

3 回答 3

1

这段代码的问题是,如果之前的调用返回 true ,则Iterator永远不应该抛出NoSuchElementExceptionfrom 。你可以考虑做这样的事情:getNexthasNext

  1. inhasNext从底层迭代器中获取下一个适当的元素并将其缓存到下一次调用next. 从next只返回缓存的值。如果缓存值为空,则hasNext不会调用,然后您可以适当地抛出 NSEE。

您可能要考虑的另一个选项是使用 GuavaIterables.transformLists.transform. 这旨在Iterable对每个元素进行并执行一些翻译。您可以返回null任何无法转换的值,并Iterables.filter使用Predicates.notNull Predicate删除不为空的元素。

Iterables.transform

Iterables.filter

谓词.notNull

使用番石榴,上面的代码将是:

Function<A, B> myFunction = new Function<A,B>(){
   public B apply(A input){
      try{ // do work
         return new B();
      catch(Exception e){ return null; }          
   }
}

Iterable<A> inputList = ...;
Iterable<B> newList = Iterables.filter(
       Iterables.transform(inputList, myFunction), 
       Predicates.notNull());

for (B b : newList)...
于 2012-10-18T10:42:41.893 回答
0

来自Iterator.hasNext()的 Javadoc

如果迭代有更多元素,则返回 true。(换句话说,如果 next() 将返回一个元素而不是抛出异常,则返回 true。)

一般来说,如果您不符合指定的 API,JVM 不会尝试再次猜测它应该做什么或执行解决方法。相反,它会产生你得到的异常或错误。

如果您包装了一个可能导致异常的迭代器,您应该抛出该异常,或者如果您觉得最好假装它从未发生过(恕我直言,这不太可能),您必须在调用 next() 之前检查这一点。

Object productValue = null;
public boolean hasNext() {
    produceValue = null;
    while (iterator1.hasNext()) {
        Object nextValue = iterator1.next();

        try {
            // do something that could raise exception
            produceValue = ....
            return true;
        } catch (Exception e) {
            // handle the exception.
        }
    }
    return false;
}

public Object next() {
    if (produceValue == null && !hasNext())
        throw new NoSuchElementException("no such element");
    Object ret = productValue;
    productValue = null;
    return ret;
}

public void remove() {
    throw new UnsupportedOperationException("cannot remove element");
}
于 2012-10-18T10:42:17.637 回答
0

How about the following?

import java.util.Iterator;

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

    private Iterator<T> iterator;
    private T nextValue;
    private RuntimeException nextException;

    /* -------------------------------------------------------------------- */

    public BreakingIterator(Iterator<T> iterator) {
        this.iterator = iterator;
        this.nextValue = null;
        this.nextException = null;
    }

    /* -------------------------------------------------------------------- */

    public boolean hasNext() {
        if (this.iterator.hasNext()) {
            try {
                this.nextValue = this.iterator.next();
            } catch (RuntimeException e) {
                this.nextValue = null;
                this.nextException = e;

                return false;
            }

            return true;
        } else {
            return false;
        }
    }

    public T next() {
        if (this.nextException != null) {
            throw this.nextException;
        } else {
            return this.nextValue;
        }
    }

    public void remove() {
        this.iterator.remove();
    }

}

This implementation will move all the hard work from next into hasNext.

于 2012-10-18T11:04:15.150 回答