在foreach循环中正确处理NoSuchElementException?

时间:2012-10-18 10:32:47

标签: java iterator loops

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

让我解释一下。

通常,迭代器需要提供两个重要的方法:hasNext和next。如果没有更多要迭代的元素,下一个方法可能会引发NoSuchElementException。 hasNext方法更像是一种语法,只是为了让你的代码更好一些。重要的方法是下一步,这是应该做任何工作的方法,如果有的话,但不是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&lt; - 这是我们原来的迭代器,一切都很好。

Iterator2&lt; - 此迭代器包围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");
    }

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

有更好的方法吗?

3 个答案:

答案 0 :(得分:0)

来自Iterator.hasNext()

的Javadoc
  

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

通常,如果您未能按照指定的方式使用API​​,JVM将不会尝试再次猜测它应该做什么,或执行变通方法。相反,它会产生你得到的异常或错误。

如果你要包装一个可能导致异常的迭代器,你应该抛出那个异常,或者如果你觉得最好假装它从未发生过(这不太可能是IMHO)你必须在调用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");
}

答案 1 :(得分:0)

此代码存在的问题是Iterator如果先前对NoSuchElementException的调用返回true,则永远不应从getNext抛出hasNext。你可能会考虑做这样的事情:

  1. hasNext中从底层迭代器获取下一个适当的元素并将其缓存直到下一次调用next。从next只返回缓存的值。如果缓存的值为null,则不会调用hasNext,然后您可以适当地抛出NSEE。
  2. 您可能要考虑的另一个选项是使用Guava的Iterables.transformLists.transform。这旨在获取Iterable并对每个元素执行一些翻译。您可以针对无法转换的任何值返回null,并使用Iterables.filter Predicates.notNull执行Predicate以删除非空元素。

    Iterables.transform

    Iterables.filter

    Predicates.notNull

    使用Guava,上面的代码是:

    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)...
    

答案 2 :(得分:0)

以下情况如何?

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();
    }

}

此实现将把所有艰苦的工作从下一步转移到hasNext。