Project Coin には,自動リソース管理 (Automatic Resource Management, ARM) を行う機能がある。コードブロックのエラー時や正常終了時にクローズが必要な,外部リソースの操作を簡単にすることが目的だ。次のような単純なファイルコピー操作を考えてみよう。Java Bytestream チュートリアル から引用したものだ。
FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("xanadu.txt"); out = new FileOutputStream("outagain.txt"); int c; while ((c = in.read()) != -1) out.write(c); } finally { if (in != null) in.close(); if (out != null) out.close(); }
ボイラープレート(定形的な処理)がたくさんあるが,それだけではない。資料によると InputStream.Close()
では IOException
がスローされる可能性があるのだ。(例外の発生する可能性は OutputStream
の方がはるかに大きい。それでもこのコードを正しくコンパイルするためには,外部に catch
ブロックを置くか,あるいは例外伝播(propagation)を宣言する必要がある。)
さらに try-catch-finally
ブロックの構文スコープの問題から,FileInputStream
変数 in と FileOutputStream
変数 out は構文上,ブロック自体の外部で定義する必要がある。(try
ブロック内部で定義すると,catch
あるいは finally
ブロックから参照できなくなる。)
このようなボイラプレートコードを排除して,ブロック内で使用されるリソースの構文スコープを強固なものにするために,Java 言語の try
ブロックに新たな仕様が追加されることになった。この try-with-resource ブロック (または ARM ブロック) の 初期仕様(initial specification) が 初期実装(initial implementation) を通じて提供され,JDK7 の ビルド105 でも採用されている。
新たなインターフェイスとして java.lang.AutoClosable
が 提案API (proposed API) に追加された。これには Exception
をスローする close()
メソッドがひとつ定義されている。このインターフェースは java.io.Closeable
の親として組み込まれてるので,すべての InputStream
と OutputStream
がこの処理の恩恵を受けられる。さらに FileLock
と ImageInputStream
にも AutoCloseable
インターフェースが適用されている。
これを使えば,先の例は次のように書き直すことができる。
try ( FileInputStream in = new FileInputStream("xanadu.txt"); FileOutputStream out = new FileOutputStream("outagain.txt") ) { int c; while((c=in.read()) != -1 ) out.write(); }
try
ブロックの最後に達すると,正常終了かどうかに関わらず out
と in
両方のリソースに対してclose()
が自動的にコールされる。さらに最初の例とは違い,out.close()
と in.close()
が共に実行されることも保証される。(最初の例では in.close()
が例外をスローした場合,その次の out.clse()
は実行されない)
この例には小さいが,しかし注目に値する特徴がいくつも存在する。
- 現時点では,リソースセクションの最後のリソースの後にセミコロン(';')を付加することはできない。
- リソースブロックを try 本体から分離するため,通常の
{}
の代わりに()
を使用する。またリソースブロックがある場合には,1つ以上のリソースを定義する必要がある。 - 各リソース定義は type var = expression の形式でなければならない。また,これ以外の一般式をリソースブロックに記述することはできない。
- リソースは暗黙的に final である。つまり
final
修飾子が存在するように動作する。リソース変数への値の設定はすべて,コンパイル時にエラーとなる。 - リソースは
AutoCloseable
のサブタイプでなければならない。この条件を満たさない場合はコンパイル時にエラーとなる。 - クローズ処理の実行順序は,リソース定義と逆順である。先程の修正後の例で言えば,
in.close()
の前にout.close()
がコールされる。これによって,ネストしたストリームを外から中へと構築し,その逆順でクローズすることが可能になるため,定義順に実行するよりも理にかなっている (例えば,下位のストリームがクローズされる前にバッファをフラッシュできる)。 - n 個のリソースを使用するブロックでは,n+1 個の例外が発生する可能性がある。これは処理ブロック本体で例外がスローされて,各リソースのクローズ処理でも例外が発生する時に起こる。この場合,本体の例外は実際にスローされるが,その他は例外クラスの 抑制例外リスト(suppressed exception list) に追加される。このリストの内容には
getSuppressedExceptions()
メソッドを通じてアクセスできる。 - Exception クラスから取得するスタックトレースには
Suppressed:
というプレフィックスが付加されている場合がある。シリアライズされたThrowable
のフォーマットも同様だ。(これは Java 6 クライアントから Java 7 ランタイム上のリモートサービスを起動する場合,あるいはその逆の場合に影響する可能性がある。) - 現時点では
javax.swing
とjava.sql
は ARM に適合していない。ARM で使用するクラスはAutoCloseable
を継承するものに限られているためだ。JDBC 4.1 が JDK 7 に含まれれば ARM がサポートされるはずだが,それがいつになるかは明確になっていない。
Java 開発者のワークフローからボイラープレートコードを除去することで,いくらかの生産性向上は達せられるだろう。しかし JDK 7 で採用されるとはいえ,これを利用したコードが記述できるようになるには,まだ多少の時間がかかりそうだ。Java 6 で実行させるためには,コンパイル時に多くのライブラリが必要になる。自動リソース管理が適用されるのは,-target 7 (または同種の) オプション指定でコンパイルされたコードに限定されるだろう。Java 6 の寿命が尽き,Java 8 がリリースされた頃には,自動的に ARM の使用が有効になっているかも知れない。