【问题标题】:Exception handling in IterableIterable 中的异常处理
【发布时间】:2011-02-26 04:51:43
【问题描述】:

在维护 foreach 语法糖的同时,有什么方法可以处理 - 并继续 - 迭代器中的异常?

我有一个解析器,它遍历文件中的行,每行返回一个类。有时行在语法上是虚假的,但这并不一定意味着我们不应该继续阅读文件。

我的解析器实现了 Iterable,但是处理潜在的异常意味着要写

for (Iterator iter = myParser.iterator(); iter.hasNext(); ) {
  try {
    MyClass myClass = iter.next();
    // .. do stuff ..
  } catch (Exception e) {
    // .. do exception stuff ..
  }
}

.. 这没有错,但是有什么方法可以对 foreach 构造中的隐式单个 iter.next() 调用进行异常处理?

【问题讨论】:

    标签: java exception foreach


    【解决方案1】:

    就我个人而言,我会使用 guava(以前的 google-collections)AbstractIterator 来执行此操作——请参阅该类的 Javadoc 以获取一个简单的 null 跳过示例,该示例可以轻松修改:

    public static Iterator<MyClass> skipExceptions(final Iterator<MyClass> in) {
      return new AbstractIterator<MyClass>() {
        protected String computeNext() {
          while (in.hasNext()) {
            try {
               return in.next();
            } catch (MalformedThingyException e) {
               // Do nothing, skip to the next one
            }
          }
          return endOfData();
        }
      };
    }}
    

    那么当使用它时,它就像这样简单:

    for (MyClass thing : skipExceptions(myParser.iterator()) { 
       // Do something ONLY to those items that didn't cause an exception
    }
    

    是的,它稍微有点冗长,但它也是通用的并且易于重用(并且在调用端非常干净)。

    【讨论】:

    • 这种方法是我建议的下一个方法,但我不知道已经存在解决方案。
    【解决方案2】:

    我很困惑。我看到了一个简单的迭代器模式,典型的 iter.hasNext() 和 iter.next() 调用并问自己,为什么需要对这些迭代器操作进行特殊的异常处理。迭代器要么有下一个元素并从集合中返回它,要么没有,这由hasNext() 调用检测到。

    所以我开始质疑一切,然后我想到,您基本上以 .. 说 .. 非常规的方式使用迭代器模式。您不是遍历集合或文件的行,而是使用 Iterator#next() 将基于文本的模型解析为对象。

    这是来自维基百科的简短定义:

    在面向对象编程中, 迭代器模式是一种设计模式 其中迭代器用于访问 聚合对象的元素 依次不暴露其 底层表示。

    对我来说,使用文本文件作为行的集合并进行迭代是可以接受的。但是迭代器应该返回文本行(字符串)。您确实应该在代码中将迭代和解析分开,如下面的 sn-ps 所示。要么您有一个解析器来解析文本文件并为 MyClass 对象提供迭代器,要么您遍历文本文件并逐行解析:

    public void snippet1(String[] lines, MyParser, myParser) {
      for (String line:lines) {
        try {
          MyClass myClass = myParser.parse(line);
        } catch (Exception e) {
          // handle/report unparsable lines
        }
      }
    }
    
    public void snippet2(String[] lines) {
        try {
          MyParser myParser = new MyParser();
          myParser.parse(lines);
        } catch (Exception e) {
          // handle/report unparsable lines
        }
        for (MyClass myClass:myParser) {
          // do something with myClass object
        }
    }
    

    【讨论】:

    • 是的。我得票最多,但实际上我比我自己更喜欢其他两个答案:-)
    【解决方案3】:

    您当然可以将您的迭代器包装在另一个迭代器中,如下所示:

    public class SafeIterator<E> implements Iterator<E>{
        private Iterator<E> inner;
        public SafeIterator(Iterator<E> inner){this.inner=inner;}
        public boolean hasNext(){return inner.hasNext();}
        public E next(){
            try{
                return inner.next();
            }catch(Exception e){return null;} //you'll also want to do some logging here
        }
    } 
    

    编辑:

    这里的问题是 a) 你违反了迭代器合约。 hasNext() 建议下一个值即将到来,而此版本可能提供也可能不提供值(可能为 null),因此客户端始终必须进行 null 检查:

    for(MyType m : myIterator){
        if(m!=null){
            // do stuff here
        }
    }
    

    这当然会带走迭代器模式的大部分优雅,但更重要的是 b) 你正在丢弃异常。我们违反了 Josh Bloch(Effective Java)第 65 条:“不要忽略异常”。我们通过忽略错误的价值观来解决错误的问题。解析器要么学会处理坏值(也许通过跳到下一个好值),要么不应该有任何坏值。

    【讨论】:

    • 显示这种方法的问题(你必须处理循环体中的空值),你会得到 +1 票。
    • 这种方法存在不止一个问题,但我会马上详细说明。
    【解决方案4】:

    在维护 foreach 语法糖的同时,有什么方法可以处理 - 并继续 - 迭代器中的异常?

    没有这种糖。

    有时行在语法上是虚假的,但这并不一定意味着我们不应该继续阅读文件。

    好吧,如果不是那么异常,那么这些行都是假的,为什么要抛出异常呢?您可以稍微修改一下迭代器。假设您当前遍历 ParsedThingy 实例,并且如果解析失败,解析器将抛出 ThingyParseException,请遍历允许您查询解析结果的包装器,如下所示:

    for (Possibly<ParsedThingy, ThingyParseException> p : parser) {
        if (p.exception() != null) handleException(p.exception());
        else doSomethingExcitingWith(p.value());
    }
    

    比看似自发返回nulls 更自我记录;它还允许您向客户端代码提供有关错误的信息。

    Possibly&lt;V, X&gt; 是一个值的包装器,实际上可能是一个异常。您可以通过检查exception() 是否为非空来查询异常状态,并通过调用value() 获取非异常情况的值(如果是异常则会抛出):

    class Possibly<V, X extends Throwable> {
        private final V value;
        private final X exception;
        public static <V, X extends Throwable> Possibly<V, X> forValue(V v){ 
            return new Possibly<V, X>(v, null); 
        }
        public static <V, X extends Throwable> Possibly<V, X> forException(X x){ 
            if (x == null) throw new NullPointerException(); 
            return new Possibly<V, X>(null, x);
        }
        private Possibly(V v, X x){ value = v; exception = x; }
        public X exception(){ return exception; }
        public V value() throws X {
            if (exception != null) throw exception;
            return value;
        }
    }
    

    那么您的iterator() 将如下所示:

    Iterator<Possibly<ParsedThingy, ThingyParseException>> parse() {
        return new Iterator<Possibly<ParsedThingy, ThingyParseException>> {
            public boolean hasNext(){ ... }
            public void remove(){ ... }
            public Possibly<ParsedThingy, ThingyParseException> next() 
                try {
                    ParsedThingy t = parseNext(); // throws ThingyParseException
                    return Possibly.forValue(t);
                } catch (ThingyParseException e) {
                    return Possibly.forException(e);
                }
            }
        };
    }
    

    有点冗长,可以通过减少通用性来避免。

    【讨论】:

      猜你喜欢
      • 2020-06-26
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多