はじめに
テキスト入出力で用いられる BufferedReader を始め、使用後に明示的に Close する必要があるクラスがあります。
Java7 以降から、try-with-resources 文を使うことで、使用後に自動で Close してくれるようになります。
ふ〜ん。分かるような分からないようなと思っていたのですが、ある方の発言で一気に理解ができるようになりました。
「C# の using 句と同じ」
言われてみればなんで気づかなかったのか、というくらいですが。
とはいえ、本当に両者は同じなのでしょうか。
この謎を探るため急遽我々はry
両者を比較してみることにしました。
[Java]基本のクラス
確認用にこのようなクラスを用意します。
BaseSampleClass.java
public class BaseSampleClass implements AutoCloseable{ private final String message; public BaseSampleClass(String message){ this.message = message; } public String getMessage() throws Exception{ System.out.println("getMessage " + message); return message; } public void close() throws Exception{ System.out.println("closed " + message); } }
- try-with-resources を使用するためには、 AutoCloseable または Closeable を継承している必要があります。
- 各メソッドの throws Exception は現状では不要ですが、あとでそれぞれのメソッドでエラーが出た場合の検証に使います。
- close メソッドは今回は呼ばれたタイミングが知りたいだけであるため、ログ出力のみ行っています。
呼び出すコードはこちら。
App.java
public class App{ public static void main( String[] args){ System.out.println("start"); try(BaseSampleClass class0 = new BaseSampleClass("class0")){ System.out.println(class0.getMessage()); } catch (Exception e){ System.out.println(e.getMessage()); } System.out.println("end"); } }
[C#]基本のクラス
C# でも同様にクラスを作っていきます。
BaseSampleClass.cs
using System; namespace ConsoleApplication1 { public class BaseSampleClass: IDisposable { private readonly string _message; public BaseSampleClass(string message) { _message = message; } public string GetMessage() { Console.WriteLine("getMessage " + _message); return _message; } public void Dispose() { Console.WriteLine("disposed " + _message); } } }
- using 句で使用するためには、 IDisposable を継承している必要があります。
Program.cs
using System; namespace ConsoleApplication1{ internal class Program{ public static void Main(string[] args){ Console.WriteLine("start"); try{ using (BaseSampleClass class0 = new BaseSampleClass("class0")){ Console.WriteLine(class0.GetMessage()); } } catch (Exception e){ Console.WriteLine(e.Message); } Console.WriteLine("end"); } } }
複数宣言
複数のインスタンスを try-with-resources で宣言するには、入れ子にすることももちろん可能ですが、下記のように書くことができます。
App.java
try(HasInnerClass class0 = new HasInnerClass(); HasInnerClass.InnerClass class1 = class0.new InnerClass()){ // 何か処理. }
Java の場合はそれぞれのインスタンスの型が同じでも違っても同じように書くことができます。
しかし、 C# では若干違いがあります。
生成したいインスタンスの型が同じ場合
using (BaseSampleClass class0 = new BaseSampleClass("class0"), class1 = new BaseSampleClass("class1")){ // 何か処理. }
生成したいインスタンスの型が異なる場合
using (BaseSampleClass class0 = new BaseSampleClass("class0")) using (HasInnerClass class1 = new HasInnerClass()){ // 何か処理. }
それぞれ別々に書く必要があるわけです。
また、 using の場合は if などと同じように {} 無しでも書くことができるため、上記のような書き方になります。
staticなインスタンス
staticなインスタンスの場合、 Java、C# ともに下記のように書くことができます (Java は ver.9 から)。
App.java
public class App{ private final static BaseSampleClass S = new BaseSampleClass("final"); public static void main( String[] args){ try(S){ // 何か処理. } catch (Exception e){ System.out.println(e.getMessage()); } System.out.println("end"); } }
実行順序
- try (using) ブロック
- close() (Dispose()) メソッド
- catch ブロック
InnerClass
InnerClass を持っていて、両方が AutoCloseable(IDisposable) を継承している場合の順序はどのようになるでしょうか。
HasInnerClass.java
public class HasInnerClass implements AutoCloseable{ public HasInnerClass(){ System.out.println("constructor HasInnerClass"); } public void close(){ System.out.println("closed HasInnerClass"); } public class InnerClass implements AutoCloseable { public InnerClass(){ System.out.println("constructor InnerClass"); } public void close(){ System.out.println("closed InnerClass"); } } }
App.java
public class App{ public static void main( String[] args){ System.out.println("start"); try(HasInnerClass class0 = new HasInnerClass(); HasInnerClass.InnerClass class1 = class0.new InnerClass()){ // 何か処理. } catch (Exception e) { System.out.println(e); } System.out.println("end"); } }
実行結果は以下の通りです。
start constructor HasInnerClass constructor InnerClass closed InnerClass closed HasInnerClass end
InnerClass -> EnclosingClass の順番に close されています。
ただし、 C# の場合は InnerClass のインスタンスを生成する場合は「HasInnerClass.InnerClass class1 = new HasInnerClass.InnerClass();」となり HasInnerClass のインスタンスが不要なため、 class1 と class0 を逆に宣言できます。
この場合、EnclosingClass -> InnerClass の順に dispose(close) されます。
つまり、後から宣言されたインスタンスが先に close(dispose) されるわけですね。
例外の抑制
これも Java, C# 共通ですが、1.の try ブロックまたは 2.の close() メソッドのどちらかで例外は発生した場合、3.の catch ブロックが実行されるわけですが、 1.と2.の両方で例外が発生した場合、2.の例外は抑制されます。
BaseSampleClass.java
public class BaseSampleClass implements AutoCloseable{ private final String message; public String getMessage() throws Exception{ throw new Exception("exception from getMessage()"); } public BaseSampleClass(String message){ this.message = message; } public void close() throws Exception{ System.out.println("closed " + message); throw new Exception("exception from close()"); } }
- getMessage()、 close() で例外を投げるよう変更しています。
これを実行すると、以下がログに出力されます。
start closed class0 exception from getMessage() end
close() で投げているはずの例外が抑制されています。
抑制された例外の取得
Java では抑制された例外を取得することができます。
App.java
public class App{ public static void main( String[] args){ System.out.println("start"); try(BaseSampleClass class0 = new BaseSampleClass("class0")){ System.out.println(class0.getMessage()); } catch (Exception e){ System.out.println(e.getMessage()); Throwable[] throwables = e.getSuppressed(); if(throwables != null){ for(Throwable t: throwables){ System.out.println(t.getMessage()); } } } System.out.println("end"); } }
これを実行すると、以下がログに出力されます。
start closed class0 exception from getMessage() exception from close() end
close() で投げた例外も受け取ることができていますね。
注意点としては getSuppressed() で取得できるのは「抑制された例外」のみであることです。
つまり、最初に投げた getMessage() の例外は取得できません。
そのため、 e.getMessage() と合わせて確認する必要があります。
次へ
長くなってきたので一旦切ります。
参照
try-with-resources
- The try-with-resources Statement (The Java™ Tutorials > Essential Classes > Exceptions)
- try-with-resources 文 - Oracle
- More concise try-with-resources statements in JDK 9 - Oracle Joseph D. Darcy's Oracle Blog
- try-with-resourcesでリソース解放されないパターン - Qiita
- Java文メモ - Hishidama's Java Statement Memo
- The C# using statement and suppressed exceptions compared to Java 7 try…with - Info Support Blog
- Java7 の try-with-resources で複数のリソースを扱うときの書き方が、C# の using と全然違う - 地平線に行く
- Java本格入門