Java7 の try-with-resources で複数のリソースを扱うときの書き方が、C# の using と全然違う
Java7 の try-with-resources と C# の using は、どちらも同じだと思っていました。
でも、複数のリソースを扱うときの書き方が全然違っていました。
// Java7, ひとつの try の中に複数の宣言を書く try(BufferedReader reader = Files.newBufferedReader(path1, charset); BufferedWriter writer = Files.newBufferedWriter(path2, charset)){ String line = reader.readLine(); (以下省略)
// C#, 複数の using の中にひとつずつ宣言を書く using (TextReader reader = File.OpenText(path1)) using (TextWriter writer = File.CreateText(path2)) { string line = reader.ReadLine(); (以下省略)
この理由を調べてみたら、意外とおもしろいことがわかりました。
理由1:try/using のうしろが違う
Java7 の try-with-resources だと後ろに "{" がないとコンパイルできません。
// Java7 (コンパイルNG:"{ が必要です。") try(BufferedReader reader = Files.newBufferedReader(path, charset)) reader.readLine();
しかし、C# の using だと "{" がなくてもコンパイルできます。
// C# (コンパイルOK) using (TextReader reader = File.OpenText(path)) reader.ReadLine();
これは、それぞれの文法が以下のようになっているからです。
- try-with-resources の後ろは「ブロック」
- メソッド文や synchronized 文と同じで "{...}"が必須
- Project Coin: Updated ARM Spec | Oracle Joseph D. Darcy's Oracle Blog
- using の後ろは「ステートメント」
- if文 や for文と同じで、"{...}"が必須ではない*1
- Download Visual Studio 2003 Retired Technical documentation from Official Microsoft Download Center
このため、try-with-resources を複数使うときはネストして書きます。
// Java7 try(BufferedReader reader = Files.newBufferedReader(path1, charset)){ try(BufferedWriter writer = Files.newBufferedWriter(path2, charset)){ String line = reader.readLine(); (以下省略)
一方、using は淡々と並べて書きます。
// C# using (TextReader reader = File.OpenText(path1)) using (TextWriter writer = File.CreateText(path2)) { string line = reader.ReadLine(); (以下省略)
理由2:複数のリソースを記述するときの文法が違う
理由1を見ると、using の方がネストが深くならずにいい!と思われるかもしれません。
でも、try-with-resources は一つの文で複数のリソースが扱えるようになっているので、そちらを使えばネストが深くなりません。
// Java7, try-with-resources try(BufferedReader reader1 = Files.newBufferedReader(path1, charset); BufferedReader reader2 = Files.newBufferedReader(path2, charset)){ String line = reader1.readLine(); (以下省略)
もちろん、C#でも同じようなことができます。
// C#, using using (TextReader reader1 = File.OpenText(path1), reader2 = File.OpenText(path2)) { string line = reader1.ReadLine(); (以下省略)
しかし、大きな違いがあります。
try-with-resources は複数の宣言文が書けますが、using は一つの宣言文しか書けません*2。
そのため、読み書きの両方をひとつの using で扱うことができません。
// C# (コンパイルNG:"for、using、fixed または declaration ステートメント // に 1 つ以上の型を使用することはできません。") using (TextReader reader = File.OpenText(path1), TextWriter writer = File.CreateText(path2)) { string line = reader.ReadLine(); (以下省略)
なので、using を複数使います。
// C# using (TextReader reader = File.OpenText(path1)) using (TextWriter writer = File.CreateText(path2)) { string line = reader.ReadLine(); (以下省略)
結論
以前、Java7のAutoCloseable - 地平線に行くという記事を書いたときに「C# の using みたいです。」と書いたんですが、詳しく見てみると全然違いますね。
同じ Automatic Resource Management(ARM) を実現するための文法なのに、こんなに違うとは思っていませんでした。
おもしろいですね!
*1:ブロックはステートメントの一種にすぎないので、単一の文なら "{...}" は必要ありません。文法 → 18.1 Syntax - The Java Language Specification, Third Edition
*2:なので、単一の型しか使えません。これは、普通に型宣言をするときに、一つの文では「File file1, file2;」というように単一の型しか宣言できないのと同じです。複数の型を宣言したければ、2つの文に分けて「File file; Path path;」とするしかありませんが、これが using ではできません。