【Java】Files系Streamはcloseが必要?

Javaでファイルやディレクトリを扱う際に、Files.findFiles.lines などの便利なメソッドを使ったことがある方は多いと思います。

しかし、こうしたメソッドが返す Stream は、実は内部でファイルやディレクトリにアクセスするリソースを保持しており、正しくクローズしないと「ファイルがロックされて削除できない」「Too many open files エラーが出る」といった問題が発生することがあります。

この記事では、Files.findFiles.linesFiles.listFiles.walk に代表される「Files系Stream」のクローズの必要性と、安全な使い方について詳しく解説します。

目次

Files系Streamはクローズが必要?

結論:はい、クローズが必要です。

Files.findFiles.linesFiles.listFiles.walk といったメソッドは、いずれも Stream を返します。見た目は単なるストリームですが、その内部ではOSレベルのリソース(ファイルディスクリプタやディレクトリハンドル)を保持しており、使用後に明示的なクローズが必要です。

クローズが必要なメソッド一覧

メソッド戻り値の型説明
Files.findStream<Path>条件に合うファイル/ディレクトリ検索。FileTreeWalker を使用
Files.linesStream<String>ファイルの行単位読み込み。BufferedReader を使用
Files.listStream<Path>ディレクトリ直下の一覧取得。DirectoryStream を使用
Files.walkStream<Path>ディレクトリの再帰的探索。FileTreeWalker を使用

なぜクローズが必要なのか?

Files.walkFiles.lines などのメソッドは、一見すると普通の Stream を返しているように見えます。しかし実際には、ファイルシステムに接続されたリソース(ディレクトリストリームやリーダーなど)を内部で開いて保持しており、明示的にクローズしなければ、それらのリソースが解放されません。

このようなストリームをクローズせずに放置すると、次のような問題が発生する可能性があります。

✅ クローズしないことで起きるトラブル

  • Windowsでのファイルロック問題
    • ファイルやディレクトリを開いたままにしていると、「使用中」と判断され、削除やリネームができなくなる。
  • Linuxでの Too many open files エラー
    • 開いたファイルディスクリプタが解放されず、OSの上限に達して例外が発生。
  • パフォーマンス劣化・予測不能な動作
    • ガベージコレクションに頼るのは不確実。クローズタイミングが制御できず、アプリが不安定に。

これらの理由から、Javadoc の各メソッドでも下記のように明記されており、try-with-resources 構文を使って明示的にクローズすることが推奨されています。

APIのノート:
このメソッドは、try-with-resources文または類似の制御構造内で使用して、ストリーム操作が完了した後にストリームのオープン・ディレクトリがすぐに閉じられるようにする必要があります。

正しい使い方:try-with-resourcesで自動クローズ

Java 7以降では、try-with-resources 構文を使うのが最も安全かつ推奨される方法です。これにより、処理の途中で例外が発生しても、ストリームが確実にクローズされます。

例1:Files.linesでファイルを1行ずつ処理

try (Stream<String> lines = Files.lines(Path.of("sample.txt"))) {
    lines.forEach(System.out::println);
}

例2:Files.walkでディレクトリを再帰的に処理

try (Stream<Path> stream = Files.walk(Path.of("dir"))) {
    stream.forEach(System.out::println);
}

これらの StreamAutoCloseable を実装しており、try-with-resource文で明示的な close() 呼び出しは不要となり、安全かつ読みやすいコードになります。

このように、Stream が不要になったタイミングで即座にクローズすることが、リソースリークを防ぐベストプラクティスです。

よくある間違いと注意点

Files系のメソッドを使う際、ストリームのクローズを忘れると、リソースリークや予期せぬ不具合の原因になります。ここでは、特によく見かけるNGパターンを紹介します。

❌ クローズせずに使い捨てる

// ストリームがcloseされず、リソースリークの可能性あり
Files.lines(Path.of("sample.txt")).forEach(System.out::println);

このように直接 forEach を呼んでしまうと、ストリームを明示的にクローズする手段がなくなります。必ず try-with-resources で囲むようにしましょう。

❌ Streamをメソッドの戻り値として返す

// 呼び出し元がcloseしなければリークの原因に
public Stream<Path> getFiles() throws IOException {
    return Files.walk(Path.of("dir"));
}

このように Stream を外部に返すと、呼び出し元にクローズの責任が移ります。しかし、呼び出し元がそのことに気づかず、クローズを忘れてしまうというのはよくあるトラブルです。ライブラリや共通モジュールなどでストリームを返す設計を行う場合は、次のような対応が重要になります。

  • Javadocに「呼び出し側で必ずクローズすること」と明記する
  • Closeableなリソースを返さない設計に変える(例:List<Path> などに変換して返す)
  • コールバック形式で処理を渡して、内部でクローズを完結させる

たとえば次のような設計にすることで、呼び出し元にクローズの責任を押し付けず、安全に利用してもらうことができます。

public void processFiles(Path dir, Consumer<Stream<Path>> processor) throws IOException {
    try (Stream<Path> stream = Files.walk(dir)) {
        processor.accept(stream);
    }
}

このようにすることで、リソースの管理責任を明確に分担させることができ、誤使用のリスクを大幅に減らせます。

まとめ:Files系Streamは「使ったら閉じる」が鉄則

Files系メソッドが返す Stream は、一見すると通常のストリームと変わらないように見えますが、実際にはOSリソースを伴うストリームです。適切にクローズしないと、リソースリークやファイルロックなどの問題を引き起こします。

  • Files.find / Files.lines / Files.list / Files.walk などはすべて明示的なクローズが必要
  • try-with-resourcesを使えば、安全かつ確実にクローズ処理を行える
  • クローズを忘れると、ファイルロックや「Too many open files」エラーなどの実害が生じる可能性がある
  • ストリームを他のメソッドに渡したり返したりする設計は注意。責任の所在が曖昧になる

「開いたストリームは必ず閉じる」――これはJavaのファイル操作における基本原則です。安全で安定したコードを書くために、リソース管理は常に意識しましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次