「アミーゴ、テンハット!」

「Javaを学べて嬉しいです、船長!」

「安心してください、アミーゴ。今日は非常に興味深いトピックがあります。Java プログラムが外部リソースとどのように対話するかについて話し、非常に興味深い Java ステートメントを 1 つ学びます。耳を塞がない方が良いです。」

"ぜひ聞きたいです。"

「Java プログラムが実行されると、Java マシンの外部のエンティティと対話することがあります。たとえば、ディスク上のファイルなどです。これらのエンティティは通常、外部リソースと呼ばれます。」

「では、内部リソースとは何でしょうか?」

「内部リソースは、Java マシン内で作成されるオブジェクトです。通常、対話は次のスキームに従います。

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

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

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

「オフィスでコーヒー マグカップが共有されていると想像してください。誰かがマグカップを手に取ると、他の人はそれを受け取ることができなくなります。しかし、そのマグカップを使用し、洗って元の位置に戻せば、誰でも再びそれを受け取ることができます。」

「わかりました。地下鉄やその他の公共交通機関の座席のようなものです。席が空いていれば、誰でも座ることができます。席が占有されている場合、その席は座った人によって管理されます。」

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

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

「それは公平ですね。」

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

「メモリを食い尽くすプログラムのようなものです...」

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

「それが良い知らせだとしたら、悪い知らせがあるということですか?」

「その通りです。悪いニュースは、サーバー アプリケーションを作成している場合...」

「しかし、そのようなアプリケーションを私が書くのでしょうか?」

「多くのサーバー アプリケーションは Java で書かれているため、仕事用に作成することになるでしょう。先ほども言いましたが、サーバー アプリケーションを作成している場合、サーバーは数日、数週間、数か月にわたってノンストップで稼働する必要があります。等。"

「言い換えれば、プログラムは終了せず、メモリが自動的に解放されないことを意味します。」

「その通りです。そして、1 日に 100 個のファイルを開いて閉じなければ、数週間以内にアプリケーションはリソース制限に達してクラッシュします。」

「これでは、数か月の安定した作業には程遠いです。何ができるでしょうか?」

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

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

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

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

「はい。すべて簡単に思えます。しかし、プログラムの実行中に例外が発生する可能性があり、外部リソースが解放されません。」

「それはとても悪いことです。どうすればいいでしょうか?」

「メソッドが常に呼び出されるようにするには、コードを--ブロックでラップし、そのブロックにメソッドを追加するclose()必要があります。これは次のようになります。trycatchfinallyclose()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ブロック内で考慮する必要があります。

「2 番目に、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 行を書き込んだだけです。」

「ふぅ……これで一件落着ということで良いですね。割と分かりやすいけど、ちょっと面倒ですよね?」

「そのとおりです。だからこそ、Java の作成者は構文糖衣を追加して私たちを支援してくれました。さあ、プログラムのハイライト、つまりこのレッスンのハイライトに移りましょう。

try-リソース付き

「第 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。または、必要ない場合は追加できません。

複数の変数を同時に

「複数のファイルを同時に開く必要がある状況がよくあります。ファイルをコピーしているとします。そのため、データのコピー元のファイルとデータのコピー先のファイルの 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";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
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();
}

「それで、ここで何を言えばいいでしょうか?tryリソースを使用することは素晴らしいことです!」

「私たちに言えることは、それを使うべきだということです。」