UncheckedIOException
Java8から UncheckedIOException という例外が追加されている。https://docs.oracle.com/javase/jp/8/docs/api/java/io/UncheckedIOException.html
これは文字通り、RuntimeExceptionを継承した非検査例外だけど、なんでこんな例外が追加されたんだろうか。
例えば、Streamを使って、ファイルの内容をすべて標準出力に出力しようとすると、こんな感じになる。
package local; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; class FilesSample { public static void main(String ... args) { sample1(); } private static void sample1() { Path path = Paths.get("./local/FilesSample.java"); try (Stream<String> s = Files.lines(path)) { s.forEach(l -> System.out.println(l)); } catch (IOException e) { e.printStackTrace(); } } }
Files.linesは指定されたパスのテキストファイルを読み取って、行ごとにStream
ファイル読み込み処理なので、例外としてはIOExceptionが発生する。
UncheckedIOException発生!
じゃあ、今度はjavaファイルではなく、classファイルを読み込ませてみよう。private static void sample2() { Path path = Paths.get("./local/FilesSample.class"); try (Stream<String> s = Files.lines(path)) { s.forEach(l -> System.out.println(l)); } catch (IOException e) { e.printStackTrace(); } }
$ java local.FilesSample Exception in thread "main" java.io.UncheckedIOException: java.nio.charset.MalformedInputException: Input length = 1 at java.io.BufferedReader$1.hasNext(BufferedReader.java:574) at java.util.Iterator.forEachRemaining(Iterator.java:115) at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at local.FilesSample.sample2(FilesSample.java:33) at local.FilesSample.main(FilesSample.java:15) Caused by: java.nio.charset.MalformedInputException: Input length = 1 at java.nio.charset.CoderResult.throwException(CoderResult.java:281) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) at java.io.InputStreamReader.read(InputStreamReader.java:184) at java.io.BufferedReader.fill(BufferedReader.java:161) at java.io.BufferedReader.readLine(BufferedReader.java:324) at java.io.BufferedReader.readLine(BufferedReader.java:389) at java.io.BufferedReader$1.hasNext(BufferedReader.java:571) ... 5 more
うーん、IOExceptionではなく UncheckedIOExceptionが発生してしまった...
しかも、発生位置はFiles.linesではなく、forEachでStreamを読み込んでいる箇所だ。
Files.linesがStream
なので、Files.linesでは例外が発生せずに、そのあとのStream.forEachで例外が発生したんだね。
でも、
Caused by: java.nio.charset.MalformedInputException
とあるように例外が発生した元々の原因はIOExceptionのサブクラスのMalformedInputExceptionだ。
Streamの評価で検査例外
じゃあ、Streamの評価中に検査例外が発生すると、どうなっちゃうんだろう。試しにこんな無理矢理なコードを用意すると
private static void sample3() { try (Stream<String> s = Stream.of("1", "2", "3")) { s.forEach(l -> { throw new IOException(); }); } }
$ javac local/FilesSample.java local/FilesSample.java:38: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります s.forEach(l -> { throw new IOException(); }); ^ エラー1個
と、見事にコンパイルエラーになってしまう。
これはStream.forEach(Consumer<? super T> action)の引数がConsumerになってて、その関数メソッドであるConsumer.acceptがthrows宣言をしていないからだね。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/function/Consumer.html
そもそも、Consumer.acceptは実装がどんな例外を発生させるのか事前にに分からないし、Streamに検査例外をケアさせようとすると、すごく煩雑な記法になってしまいそうな気がする...
なので、結局、こんな感じで検査例外を非検査例外でラッピングすることになる。
private static void sample4() { try (Stream<String> s = Stream.of("1", "2", "3")) { s.forEach(l -> { try { throw new IOException(); } catch (IOException e) { throw new UncheckedIOException(e); } }); } }
$ java local.FilesSample Exception in thread "main" java.io.UncheckedIOException: java.io.IOException at local.FilesSample.lambda$sample4$2(FilesSample.java:49) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at local.FilesSample.sample4(FilesSample.java:45) at local.FilesSample.main(FilesSample.java:14) Caused by: java.io.IOException at local.FilesSample.lambda$sample4$2(FilesSample.java:47) ... 4 more
IOExceptionはいたるところに登場するので、Streamが導入されたJava8ではUncheckedIOExceptionも作られたんだね。
IOException処理のラッパー
さて、IO処理で毎回こんな例外処理を書くのもなんなので、IOExceptionをラッピングするConsumerを作っておくといいかもしれない。package local; import java.io.IOException; import java.io.UncheckedIOException; import java.util.function.Consumer; import java.util.Objects; @FunctionalInterface interface IOExceptionConsumer<T> { void accept(T t) throws IOException; static <T> Consumer<T> toUnchecked(IOExceptionConsumer<? super T> action) { Objects.requireNonNull(action); return t -> { try { action.accept(t); } catch (IOException e) { throw new UncheckedIOException(e); } }; } }
これで、先ほどの処理は以下のようになる。
private static void sample5() { try (Stream<String> s = Stream.of("1", "2", "3")) { s.forEach(IOExceptionConsumer.toUnchecked(l -> { throw new IOException(); })); } }
0 件のコメント:
コメントを投稿