如何处理 Iterable.forEach 中的 IOException?

How to handle IOException in Iterable.forEach?

我正在研究将一组对象写入文件的方法。为什么以下使用 Iterable.forEach() 的实现无法编译?在 Eclipse 中,我收到一条消息,指出未处理 IOException。这特别令人困惑,因为我似乎确实在处理 IOExceptions。

  public void write(Iterable<?> objects) {
    try (BufferedWriter bw = new BufferedWriter(
        new OutputStreamWriter(
            new FileOutputStream("out.txt"), "UTF-8"));) {

      objects.forEach((o) -> bw.write(o.toString())); //Unhandled exception type IOException

  } catch (IOException e) {
    //handle exception
  }
}

显然,下面的方法有效。我对为什么上述方法不起作用以及如何解决它很感兴趣。

for (Object o : objects) { bw.write(o.toString()); }

我查看了 Consumer and Iterable 文档,他们似乎都没有建议如何解决这个问题。

问题是接口Consumer中的方法accept没有声明抛出异常。因此,您不能使用在 lambda 中抛出已检查异常的方法。

解决方案是改为使用 for each 循环。

先行者:The Catch or Specify Requirement.

让我们将 lambda 写成匿名的 class:

objects.forEach(new Consumer<Object>() {
    public void accept(Object o) {
        bw.write(o.toString());
    }
});

我们在处理吗?应该清楚我们不是。

当我们写一个 lambda 表达式时,我们是在声明一个方法的主体。我们也不能为 lambda 声明 throws 子句。

唯一的 "way around it" 是做如下的事情:

try (BufferedWriter bw = new BufferedWriter(
    new OutputStreamWriter(
        new FileOutputStream("out.txt"), "UTF-8"));) {

    objects.forEach((o) -> {
        try {
            bw.write(o.toString()));
        } catch(IOException kludgy) {
            throw new UncheckedIOException(kludgy);
        }
    });

} catch (UncheckedIOException kludgy) {
    IOException cause = kludgy.getCause();
    // handle exception
}

(另见 UncheckedIOException。)

Iterable.forEach 保证像该示例中那样包装和抛出异常:

Exceptions thrown by the action are relayed to the caller.

但是,最好避免在抛出已检查异常的上下文中使用 forEach,或者在 lambda 主体中捕获并处理异常。

如果您只关心将字符串打印到文件中,请使用 PrintStreamPrintWriter 而不是其他 Writer 类。 PrintStreamPrintWriter 的显着特点是它们的打印操作不会抛出 IOException。他们还自动调用对象上的 toString,这让事情变得非常方便:

public void write1(Iterable<?> objects) {
    try (PrintStream ps = new PrintStream("printout.txt", "UTF-8")) {
        objects.forEach(ps::println);
    } catch (IOException ioe) {
        // handle
    }
}

如果您担心错误,您可以调用 PrintStream.checkError,尽管这不会告诉您有关可能发生的任何错误的任何细节。

不过,普遍的问题仍然存在,如果您想从不允许它的上下文(例如 forEach)中调用抛出异常的方法,该怎么办。这只是烦人的处理,尽管只是适度的。但是,它确实需要一些设置。假设我们想写一个 Consumer 抛出 IOException。我们必须声明我们自己的功能接口:

interface IOConsumer<T> {
    void accept(T t) throws IOException;
}

现在我们需要编写一个函数,将 IOConsumer 转换为 Consumer。它通过将它捕获的任何 IOException 转换为一个 UncheckedIOException,一个为此目的创建的异常来做到这一点。

static <T> Consumer<T> wrap(IOConsumer<? super T> ioc) {
    return t -> {
        try {
            ioc.accept(t);
        } catch (IOException ioe) {
            throw new UncheckedIOException(ioe);
        }
    };
}

有了这些,我们现在可以重写原来的例子如下:

public void write2(Iterable<?> objects) {
    try (BufferedWriter bw = new BufferedWriter(
             new OutputStreamWriter(
                 new FileOutputStream("out.txt"), "UTF-8"))) {
        objects.forEach(wrap(o -> bw.write(o.toString())));
    } catch (IOException|UncheckedIOException e) {
        //handle exception
    }
}

一般情况下,如果出现任何异常,我们不会将数据写入文件。牢记这一点,我们可以写 own consumer which will wrap the checked exception into an unchecked exception。这样你就可以摆脱编译时错误。 请尝试下面的代码

import java.io.*;
import java.util.*;
public class HelloWorld{

     public static void main(String []args){
        write(Arrays.asList(1,2,3));
     }

  public static void write(Iterable<?> objects) {
    try (BufferedWriter bw = new BufferedWriter(
        new OutputStreamWriter(
            new FileOutputStream("c:\out.txt"), "UTF-8"))) {

      objects.forEach(o->myConsumerThrowsRuntimeException(bw,o.toString())); //Unhandled exception type IOException

  } catch (Exception e) {
    //handle exception
  } 
  }
  public static void myConsumerThrowsRuntimeException(Writer writer , String data){
      try{
          writer.write(data);
      }catch(IOException e){

          throw new RuntimeException("Cannot write Data error is "+e.getMessage());
      } 
  } 
}