Java

Java9の新try-with-resources

こんにちは。

Java7から導入されたtry-with-resources。イイですよね、便利ですネ簡素にかけますネ。
しかも、Java9になると、さらにスマートな書き方が導入されました。
だ...だけど、ちょっとハマるというか、残念なことがあったので、記事にしてみました。

まずは、Java7とJava8のtry-with-resourcesの書き方

書籍「Effective Java -Third Edition-(Joshua Bloch著)」の36ページに掲載されてあるfirstLineOfFileメソッドを、ここに掲載します。mainメソッドは私のオリジナルです。

Java7と8のtry-with-resources
public class Java7and8TryWithResources {
    public static void main(String[] args) {
        String val = firstLineOfFile("test.txt", "-------");
        System.out.println(val);
    }

    static String firstLineOfFile(String path, String defaultVal) {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        } catch (IOException e) {
            return defaultVal;
        }
    }
}

この書籍、Java9についても対応したよ!と謳い文句にあるのですが、try-with-resourcesに関しては上記のサンプルコードを掲載したどまりでおしまい。ちょっと残念。

Java9から書けるようになった新try-with-resources構文

Java9から書けるようになった新try-with-resources構文では、変数宣言はtry()の中ではなく、try構文の前で書けるようになりました。

Java9の新try-with-resources構文
public class Java9NewTryWithResources {
    public static void main(String[] args) {
        String val = "-------";
        try {
            val = firstLineOfFile("test.txt", val);
        } catch (FileNotFoundException e) {
            // 略
        }
        System.out.println(val);
    }

    static String firstLineOfFile(String path, String defaultVal) throws FileNotFoundException {
        BufferedReader br = new BufferedReader(new FileReader(path)); // ここ注目!😲
        try (br) { // ここも注目!😲
            return br.readLine();
        } catch (IOException e) {
            return defaultVal;
        }
    }
}

さて、この新構文ですが、オフィシャルな出典を探そうと試みました。
More concise try-with-resources statements in JDK 9
ブログなのですが、ここはURLがoracle.comなので、一応公式なのかな、と。

【1日遅れで追記】オフィシャルな出典を見つけました。Java Language Changes for Java SE 9です。でも、すぐ上のブログと同じことしか書いてないや。

https://docs.oracle.com/javase/9/https://docs.oracle.com/javase/10/ も見たんですが、そこにリンク張ってある「Java Tutorials」が現在、

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases.
(JavaチュートリアルはJDK 8向けに書かれています。このページで説明されている例とプラクティスは、それ以降のリリースで導入された改良点を利用していません。)

とあり、ズッコケ🤷です。そのうちJDK 9向けのチュートリアルが掲載されることを祈ります。:milky_way:

公式ではないところのサイトだと、

が見つかりました。

嬉しいことと、残念だと思うこと

上掲のJava9NewTryWithResources.javaとJava7and8TryWithResources.javaとを比較してみます。

嬉しいこと

うわっ…私のtry()の中、短すぎ…?! shock_woman.png

たったこれだけで、イイの?!イイんです。クゥ~!👍
BufferedReader br = new BufferedReader(new FileReader(path));
try (br) {

残念なところ

FileNotFoundExceptionをスローするnew FileReader(path)のせいで、以下の2点が残念に思います。

throws句が付いちゃったよ😧
static String firstLineOfFile(String path, String defaultVal) throws FileNotFoundException {
mainの中が行数増えたよ😩
String val = "-------";
try {
    val = firstLineOfFile("test.txt", val);
} catch (FileNotFoundException e) {
     // 略
}
System.out.println(val);

try-with-resourcesは何がために?

:musical_note:あなたと呼べば あなたと答える:notes:

みなさんご存知の名曲、ディック・ミネと星玲子の『二人は若い』の冒頭ですね。懐かしい。作詞はさすがサトウハチロー先生です。

それになぞらえて、私はこう歌ってしまいます。

:musical_note:「try」と呼べば「例外」と答える。:notes:

ところが!です。「try-with-resources」は、ただの「try」ではないのです。AutoClosableが為にあるのです。そこを失念してはいけません!
くだんのnew FileReader(path)にイチャモンを付けましたけど、だってこれ、try { … } の中に書いてないもん!BufferedReaderをオートクローズしてもらいたくてtry-with-resources書いたんだもん!

こんな書き方もできる

でも、FileReaderAutoClosableなんです。なので、以下のようにも書けます。

Java9NewTryWithResourcesを一部書き換えてみた
public class Java9NewTryWithResources2 {
    public static void main(String[] args) {
        String val = "-------";
        try {
            val = firstLineOfFile("test.txt", val);
        } catch (FileNotFoundException e) {
            // 略
        }
        System.out.println(val);
    }

    static String firstLineOfFile(String path, String defaultVal) throws FileNotFoundException {
        FileReader fr = new FileReader(path);
        BufferedReader br = new BufferedReader(fr);
        try (fr; br) { // 複数並べて書けます
            return br.readLine();
        } catch (IOException e) {
            return defaultVal;
        }
    }
}

これでもfirstLineOfFileメソッドにthrows FileNotFoundExceptionを付けざるをえないんですけどね。つまり、

コンストラクタ/メソッド 例外 どうする?
BufferedReaderのコンストラクタ 例外をスローしない try-with-resourcesでオートクローズしてもらうだけでOK
FileReaderのコンストラクタ FileNotFoundExceptionをスローする メソッドのシグネチャにthrows付ける
BufferedReaderreadLineメソッド IOExceptionをスローする catchする

この三者三様に気づかないと、「え?なんでbrはくて、frはダメなの?」とか、「何が為のIOExceptionをキャッチ?しかもFileNotFoundExceptionってIOExceptionのサブクラスだし...」などとヤキモキしてしまうことになります。

まとめ

Java9の新try-with-resources構文、いんだか悪いんだか。悩みます。まとめになってなくて、すみません。🙇

以上です。