1. 外部リソース

Java プログラムが実行されると、Java マシンの外部のエンティティと対話することがあります。たとえば、ディスク上のファイルの場合です。これらのエンティティは通常、外部リソースと呼ばれます。内部リソースは、Java マシン内で作成されるオブジェクトです。

通常、対話は次のスキームに従います。

Try-with-resources ステートメント

リソースの追跡

オペレーティング システムは、利用可能なリソースを厳密に追跡し、さまざまなプログラムからのリソースへの共有アクセスも制御します。たとえば、あるプログラムがファイルを変更した場合、別のプログラムはそのファイルを変更 (または削除) できません。この原則はファイルに限定されませんが、最もわかりやすい例が示されています。

オペレーティング システムには、プログラムがリソースを取得および/または解放できるようにする機能 (API) があります。リソースがビジー状態の場合、そのリソースを取得したプログラムのみがそのリソースを操作できます。リソースが無料の場合、どのプログラムでもそれを取得できます。

あなたのオフィスにコーヒーマグが共有されていると想像してください。誰かがマグカップを手に取ると、他の人はそれを受け取ることができなくなります。しかし、マグカップを一度使用し、洗って元の場所に戻せば、誰でも再びそれを手に取ることができます。バスや地下鉄の座席も同様です。席が空いていれば誰でも座ることができます。席が占有されている場合、その席はその席に座った人によって管理されます。

外部リソースの取得

Java プログラムがディスク上のファイルの操作を開始するたびに、Java マシンはオペレーティング システムにそのファイルへの排他的アクセスを要求します。リソースが空いている場合は、Java マシンがそれを取得します。

ただし、ファイルの操作が終了したら、このリソース (ファイル) を解放する必要があります。つまり、このリソースが不要になったことをオペレーティング システムに通知する必要があります。これを行わないと、リソースはプログラムによって保持され続けます。

オペレーティング システムは、実行中の各プログラムが占有するリソースのリストを維持します。プログラムが割り当てられたリソース制限を超えると、オペレーティング システムは新しいリソースを提供しなくなります。

幸いなことに、プログラムが終了すると、すべてのリソースが自動的に解放されます (これはオペレーティング システム自体が行います)。

悪いニュースは、サーバー アプリケーションを作成している場合 (多くのサーバー アプリケーションは Java で作成されている場合)、サーバーは数日、数週間、または数か月にわたって停止せずに実行できる必要があることです。また、1 日に 100 個のファイルを開いて閉じなければ、数週間以内にアプリケーションはリソース制限に達し、クラッシュします。これでは数か月にわたる安定した勤務には遠く及ばない。


2.close()方法

外部リソースを使用するクラスには、それらを解放するための特別なメソッドがありますclose()

以下に、ファイルに何かを書き込み、完了したらファイルを閉じるプログラムの例を示します。つまり、オペレーティング システムのリソースを解放します。次のようになります。

コード ノート
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
ファイルへのパス。
ファイルオブジェクトの取得:リソースを取得します。
ファイルに書き込みます
ファイルを閉じます - リソースを解放します

ファイル (または他の外部リソース) を操作した後、close()外部リソースにリンクされているオブジェクトのメソッドを呼び出す必要があります。

例外

すべては単純なことのように思えます。ただし、プログラムの実行中に例外が発生し、外部リソースが解放されない場合があります。それはとても悪いことです。

メソッドが常に呼び出されるようにするにはclose()、コードをtry--ブロックでラップし、メソッドをブロックに追加する必要があります。次のようになります。catchfinallyclose()finally

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

output変数はブロック内で宣言されておりtry {}、ブロック内で表示されないため、このコードはコンパイルされませんfinally

それを修正しましょう:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

それは問題ありませんが、オブジェクトの作成時にエラーが発生すると機能しませんFileOutputStream。これは非常に簡単に発生する可能性があります。

それを修正しましょう:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

まだいくつかの批判があります。まず、オブジェクトの作成時にエラーが発生した場合FileOutputStreamoutput変数は null になります。この可能性はブロック内で考慮する必要がありますfinally

次に、close()メソッドは常にブロック内で呼び出されますfinally。これは、メソッドがブロック内で呼び出される必要がないことを意味しますtry。最終的なコードは次のようになります。

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

省略できるブロックを考慮しない場合でもcatch、3 行のコードは 10 行になります。しかし、基本的にはファイルを開いて 1 行を記述するだけです。少し面倒だと思いませんか。


3. try-リソース付き

そしてここで、Java の作成者は私たちに構文上の糖分をふりかけることにしました。第 7 バージョン以降、Java には新しいtry-with-resources ステートメントが追加されました。

これは、メソッドの必須呼び出しの問題を解決するために正確に作成されましたclose()。一般的なケースは非常に単純に見えます。

try (ClassName name = new ClassName())
{
     Code that works with the name variable
}

try これはステートメントの別のバリエーションです。tryキーワードの後に​​かっこを追加し、かっこ内の外部リソースを含むオブジェクトを作成する必要があります。括弧内のオブジェクトごとに、コンパイラーはfinallyセクションとメソッド呼び出しを追加しますclose()

以下に 2 つの同等の例を示します。

ロングコード try-with-resources を使用したコード
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

-with-resourcesを使用したコードはtryはるかに短く、読みやすくなっています。また、コードが少なければ少ないほど、タイプミスやその他のエラーが発生する可能性が低くなります。

ちなみに、 -with-resources ステートメントにはcatchfinallyブロックを追加できますtry。または、必要がない場合は追加できません。



4. 同時に複数の変数

ところで、複数のファイルを同時に開く必要がある場合がよくあります。ファイルをコピーしているとします。そのため、データのコピー元のファイルとデータのコピー先のファイルという 2 つのオブジェクトが必要です。

この場合、try-with-resources ステートメントを使用すると、その中に 1 つだけのオブジェクトを作成できます。オブジェクトを作成するコードはセミコロンで区切る必要があります。このステートメントの一般的な外観は次のとおりです。

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

ファイルのコピー例:

ロングコード ショートコード
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

さて、ここで何を言えばいいでしょうか?try-with-resources は素晴らしいことです。


5.AutoCloseableインターフェース

しかし、それだけではありません。注意深い読者は、この記述の適用方法を制限する落とし穴をすぐに探し始めるでしょう。

しかし、tryクラスにメソッドがない場合、-with-resources ステートメントはどのように機能するのでしょうかclose()? さて、何も呼び出されないとします。方法も問題もありません。

しかし、tryクラスに複数のclose()メソッドがある場合、-with-resources ステートメントはどのように機能するのでしょうか? そして、引数を渡す必要があるのでしょうか? そして、クラスにはclose()パラメータのないメソッドはありませんか?

皆さんがこれらの質問を自分自身に、そしておそらくはさらに他の質問を実際に自問していただければ幸いです。

このような問題を回避するために、Java の作成者は という特別なインターフェイスを考案しましたAutoCloseable。このインターフェイスにはメソッドが 1 つだけありclose()、パラメータはありません。

また、-with-resources ステートメントでリソースとして宣言できるのは、実装するクラスのオブジェクトのみであるAutoCloseableという制限も追加されました。try結果として、そのようなオブジェクトには常にclose()パラメーターのないメソッドが存在します。

ところで、 -with-resources ステートメントで、クラスにパラメーターのない独自のメソッドがあるが実装されていないtryオブジェクトをリソースとして宣言することは可能だと思いますか?close()AutoCloseable

悪いニュース:正解は「いいえ」です。クラスはAutoCloseableインターフェースを実装する必要があります。

良いニュース: Java にはこのインターフェースを実装するクラスが多数あるため、すべてが正常に動作する可能性が非常に高くなります。