[Java]Streamを閉じないとリソースリークするよ!っていう話(AutoClosable)
本記事は、JJUGにて登壇したものを、記事向けに加筆修正したものです。
その際の動画・スライドはこちらのページでご覧頂けます。
beppy.hatenablog.com
StreamのAPI仕様確認
StreamのAPI仕様を確認すると、AutoClosableがついています
AutoClosableがついているということは、以下のように、try-with-resources文にて、リソースとして宣言ができます。
(このケースでは、こう書き換えても何もメリットはないのですが……。)
Stream.of(1, 2, 3).forEach(System.out::println); ↓ try (Stream<Integer> stream = Stream.of(1, 2, 3)) { stream.forEach(System.out::println); } > Task :run 1 2 3
AutoClosableが必要となるケース
ではなぜこれが用意されているのかというと、 Readerや、DBのConnection等のリソースを、Stream内部で使用している場合に、使い終わった時にcloseができるようにするため です。
怠ると、リソースやコネクションのリークにつながる可能性があります。
java.nio.file.Files
list
public static Stream
list(Path dir) throws IOException
ディレクトリ内のエントリを要素に持つ遅延設定Streamを返します。 リストは再帰的ではありません。
このメソッドは、 try-with-resources文または類似の制御構造内で使用して、ストリーム操作が完了した後にストリームのオープン・ディレクトリがすぐに閉じられるようにする必要があります。
lines
public static Stream
lines(Path path, Charset cs) throws IOException
ファイル内のすべての行をStreamとして読み取ります。
返されるストリームは、Readerをカプセル化します。 ファイル・システム・リソースのタイムリな破棄が必要な場合は、try-with-resources構文を使用して、ストリーム操作の完了後にストリームのcloseメソッドが呼び出されるようにしてください。
jOOQ(ORM)
ResultQuery
Stream
stream() throws DataAccessException
……
Clients should ensure the Stream is properly closed, e.g. in a try-with-resources statement:
try (Streamstream = query.stream()) {
// Do things with stream
}
close処理を付与したいなら
上記のようなtry-with-resources文で閉じてくれるStreamを自作したい場合、onClose()
を使用することで作成することができます。
S onClose(Runnable closeHandler)
追加のクローズ・ハンドラを含む同等のストリームを返します。
クローズ・ハンドラはストリーム上でclose()メソッドが呼び出されたときに実行されますが、ハンドラの実行順序は追加された順番になります。
いずれかのクローズ・ハンドラから例外がスローされても、すべてのクローズ・ハンドラが実行されます。
……
これは中間操作です。
例えば、以下のようなコードを実行すると、try-with-resource文を抜けたときに、追加された順番でハンドラが実行されていることが確認できます。
Runnable first = () -> System.out.println("first called"); Runnable second = () -> System.out.println("second called"); try (Stream<Integer> stream = Stream.of(1, 2, 3).onClose(first).onClose(second)) { stream.forEach(System.out::println); } > Task :run 1 2 3 first called second called
もっと詳しく知りたい場合、前述したFiles.lines()
の実装が参考になりそうなので、併せてご確認下さい
最後に
恥ずかしながら、私はStreamが閉じれることを知らず、特にjOOQのは見逃す寸前で、危うくコネクションリークを起こすところでした。
改めて、ドキュメントやJavaDocはちゃんと読んで、理解してから使う必要があるなと、反省しました。
それでももし見落としたとしても、本知識があれば「このStreamは外部のリソースを使ってそうだけど大丈夫かな……?」と、注意するきっかけとなると思います。
本記事が、皆様のよいJava Lifeの助けになれば幸いです。
参考・出典
BaseStream (Java SE 17 & JDK 17)
Java8 Stream APIの基本(8) - 終端操作3(close) - エンタープライズギークス (Enterprise Geeks)