在Java中,您如何编写可能抛出异常的Iterable的等价物?

时间:2009-01-19 08:55:02

标签: java exception

在java中,一个类可以实现Iterable,它允许你使用foreach()语句和迭代合成糖:

for(T t:ts) ...

但是,这不允许您在迭代器的构造上抛出异常。如果您正在迭代网络,文件,数据库等,那么能够抛出异常会很好。明显的候选者是java.io.InputStream,Reader和java.nio.Channel代码,但这些都不能像Iterable接口那样使用Generics。

这种情况是否有常见的习惯用法或Java API?

澄清:这是询问是否存在用于从非内存源迭代对象的模式或替代接口。正如响应者所说,不推荐使用RuntimeExceptions来解决问题,也不建议我使用。

编辑2:感谢答案到目前为止。共识似乎是“你不能”。因此,我可以将问题扩展到“在这种情况下,如果这种情况有用,你会做什么?”只需编写自己的界面?

5 个答案:

答案 0 :(得分:4)

像网络这样的流在传统意义上并不是真正可迭代的。数据可以随时通过,因此对每个循环都没有意义。

对于文件读取或数据库快照(如选择查询),您没有理由不能获取该数据,将其分段为逻辑块并实现可迭代接口。

如果这是一个问题,您也可以先调用初始化方法来捕获任何异常。

try{
   ts.initializeIOIterator();
}catch(...)

for(T t:ts)
...    

答案 1 :(得分:4)

不幸的是你做不到。有两个问题:

  • Iterator API不会声明要抛出的任何异常,因此您必须抛出RuntimeExceptions(或非异常throwables)
  • 增强的for循环不会尝试在循环结束时释放资源

这很烦人。例如,在C#中,您可以轻松地编写代码来迭代文本文件的行:

public static IEnumerable<string> ReadLines(string filename)
{
    using (TextReader reader = File.OpenText(filename))
    {
        string line;
        while ( (line=reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

用作:

foreach (string line in ReadLines("foo.txt"))

foreach循环在finally块中的Dispose上调用IEnumerator,这转换为“检查我们是否需要在迭代器块中最终执行任何操作(来自using语句)”。显然,C#中没有经过检查的异常,因此事情的这一方面也不是问题。

由于这个原因,整个(有用的!)成语在Java中几乎不可行。

答案 2 :(得分:1)

您可以做的最好的事情是创建RuntimeIOException,如果出现错误,您将从hasNext / next实现中抛出。

try {
  for (...) {
    // do my stuff here
  }
catch (RuntimeIOException e) {
  throw e.getCause(); // rethrow IOException
}

RuntimeIOException将是运行时异常,包装您的IOException:

class RuntimeIOException extends RuntimeException {
  RuntimeIOException(IOException e) {
    super(e);
  }

  IOException getCause() {
    return (IOException) super.getCause();
  }
}

有时没有别的办法。

答案 3 :(得分:0)

我会说你不能,即使你可能不应该。你可以从这些东西中获取字节,如果它们在for循环中使用,那么每个字节最终都会被装箱。

您可以做的是在未经检查的异常中包装已检查的异常并遵守可迭代的接口,但这也是不可取的。

答案 4 :(得分:0)

通常在这种情况下,我会在Iterable的实现中抛出RuntimeException的适当子类。

在清理资源方面,try - finally块的工作方式与包装任何其他代码周围的foreach块一样,因此从客户端的角度来看,它可以轻松地使用它来清理任何资源。如果你想管理Iterable中的资源,它可能会更棘手,因为没有明显的开始和结束生命周期点。

在这种情况下,您可能做的最好的事情就是按需创建资源(即第一次调用next()),然后在调用{{{next()正文中抛出异常时,1}}即将返回false。这样做当然意味着当你的next()方法以异常退出时,迭代器不能再被使用 - 这不是一个不合理的约束(考虑异常是一个更错误的y版本返回{{1} })但是你应该记录的东西,因为接口没有严格限制。

也就是说,上面假设您正在创建作为Iterable。我发现在实践中,当我在类上实现Iterable时,它更像是一个“超级getter”(即客户端方便地访问存储在其中的信息的方式),而不是类本身的重点。大多数情况下,这些对象将被独立设置并通过其他方法访问,因此它们的生命周期可以完全独立于它们作为Iterable的存在而进行管理。

这似乎与问题相关,但问题的直接答案是直截了当的(“使用运行时异常”) - 棘手的部分是在存在这些异常的情况下维持适当的状态。