Javaでファイルやディレクトリを扱う際に、Files.find
や Files.lines
などの便利なメソッドを使ったことがある方は多いと思います。
しかし、こうしたメソッドが返す Stream
は、実は内部でファイルやディレクトリにアクセスするリソースを保持しており、正しくクローズしないと「ファイルがロックされて削除できない」「Too many open files
エラーが出る」といった問題が発生することがあります。
この記事では、Files.find
・Files.lines
・Files.list
・Files.walk
に代表される「Files系Stream」のクローズの必要性と、安全な使い方について詳しく解説します。
Files系Streamはクローズが必要?
結論:はい、クローズが必要です。
Files.find
、Files.lines
、Files.list
、Files.walk
といったメソッドは、いずれも Stream
を返します。見た目は単なるストリームですが、その内部ではOSレベルのリソース(ファイルディスクリプタやディレクトリハンドル)を保持しており、使用後に明示的なクローズが必要です。
クローズが必要なメソッド一覧
メソッド | 戻り値の型 | 説明 |
---|---|---|
Files.find | Stream<Path> | 条件に合うファイル/ディレクトリ検索。FileTreeWalker を使用 |
Files.lines | Stream<String> | ファイルの行単位読み込み。BufferedReader を使用 |
Files.list | Stream<Path> | ディレクトリ直下の一覧取得。DirectoryStream を使用 |
Files.walk | Stream<Path> | ディレクトリの再帰的探索。FileTreeWalker を使用 |
なぜクローズが必要なのか?
Files.walk
や Files.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);
}
これらの Stream
は AutoCloseable
を実装しており、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のファイル操作における基本原則です。安全で安定したコードを書くために、リソース管理は常に意識しましょう。