ファイルの存在確認と削除

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

ファイルの存在確認と削除 とは

Files.exists / Files.delete などの基本 API を使い、一時ファイルを作って存在を確認し、削除してからもう一度確認するコードを書いてみよう。

ファイルが「ある」「ない」を確かめる

業務アプリでも個人ツールでも、ファイルを扱うコードを書いていると「指定したパスにファイルがあるかどうか確かめたい」「使い終わったら確実に消したい」という場面に必ず出会います。Java では java.nio.file.Files というユーティリティクラスがこの手の操作を一手に引き受けてくれます。本章では Files.exists Files.createFile Files.delete を中心に、ファイルの存在確認と後片付けの作法を学んでいきましょう。

Java の世界には古くは java.io.File という API がありましたが、現代のコードでは java.nio.file.Pathjava.nio.file.Files の組み合わせを使うのが標準です。例外の扱いやシンボリックリンク対応がしっかりしており、WindowsLinux の差異も吸収してくれます。

Path はファイルやディレクトリの「位置」を表す抽象化で、Files クラスは Path を受け取って実際の操作を行います。後者は static メソッドの集まりなので、new Files() のようにインスタンス化することはありません。Files.exists(p) Files.delete(p) のように、FilesPath を渡すスタイルで使います。

diagram (will load when visible)

Files.exists と仲間たち

まず存在確認系の API を整理します。よく使うのは次の 4 つです。

  • Files.exists(p) — ファイルやディレクトリが存在すれば true
  • Files.notExists(p) — 存在しないと確認できれば true
  • Files.isRegularFile(p) — 通常のファイル (ディレクトリやリンクではない) なら true
  • Files.isDirectory(p) — ディレクトリなら true

ここで「exists の逆は notExists じゃないの?」と思うかもしれませんが、Java の世界では !Files.exists(p)Files.notExists(p) は厳密には同じではありません。アクセス権限が無くて状態が確かめられない場合、両方とも false を返すことがあるからです。

Java

import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class ExistsDemo { public static void main(String[] args) { Path p = Paths.get("sample.txt"); System.out.println(Files.exists(p)); // true / false System.out.println(Files.notExists(p)); // true / false System.out.println(Files.isRegularFile(p)); // true ならファイル System.out.println(Files.isDirectory(p)); // true ならディレクトリ } }

「存在するかどうか」と「アクセス権があるかどうか」は別問題です。Files.isReadable(p) Files.isWritable(p) を使うと、現在のプロセスがそのパスを読み書きできるかを確認できます。本番サーバーで権限エラーに悩んだら最初に疑うポイントです。

Files.createFileFiles.delete

次に、作成と削除の API を見ていきます。代表的なのは下記のとおりです。

  • Files.createFile(p) — 指定したパスに空のファイルを作る。既に存在すると例外
  • Files.createDirectories(p) — ディレクトリを作る (途中の親ディレクトリも自動で作る)
  • Files.delete(p) — ファイル / 空のディレクトリを削除する。存在しないと例外
  • Files.deleteIfExists(p) — 存在すれば削除し true、無ければ何もせず false

例として、一時ファイルを作って消すまでの一連の流れを書いてみます。

Java

import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class CreateDelete { public static void main(String[] args) throws Exception { Path p = Paths.get("work.tmp"); Files.createFile(p); System.out.println(Files.exists(p)); // true Files.delete(p); System.out.println(Files.exists(p)); // false } }

Files.createFileFiles.deleteIOException を投げ得るので、メソッドのシグネチャには throws IOException (もしくは throws Exception) が必要です。今回の課題では、後ろで触れる Files.createTempFile を使うと一時ファイル用の安全なパスを自動で割り当ててくれるので、命名の衝突を気にせずに済みます。

Files.createTempFile(prefix, suffix) は OS の一時ディレクトリに prefix1234567.suffix のような一意な名前のファイルを作ってくれます。テストや一回限りの計算結果を書き出すときの定番です。

ファイル属性を覗いてみる

Files クラスはファイルの属性 (サイズ最終更新時刻) も取得できます。よく使うのは次の通りです。

  • Files.size(p) — バイトサイズを long で返す
  • Files.getLastModifiedTime(p) — 最終更新時刻を FileTime で返す
  • Files.isReadable(p) / Files.isWritable(p) / Files.isExecutable(p) — アクセス権の確認

属性を一気に取りたいときは Files.readAttributes(p, BasicFileAttributes.class) を使うと 1 回のシステムコールで済むので効率的です。ファイル一覧のような繰り返し処理ではこちらを検討しましょう。

Java

import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; public class AttrDemo { public static void main(String[] args) throws Exception { Path p = Path.of("sample.txt"); if (Files.exists(p)) { BasicFileAttributes a = Files.readAttributes(p, BasicFileAttributes.class); System.out.println("size: " + a.size()); System.out.println("updated: " + a.lastModifiedTime()); } } }

削除フローの全体像

ここまでに登場した API を使って、ファイル削除の一般的な手順を図にまとめておきます。Files.deleteIfExists を使うと、存在チェックと削除をひとつにまとめられるので、レースコンディションを避けやすくなります。

diagram (will load when visible)

よくある間違い

ファイル操作は OS との境界線にあるため、初学者が踏みやすい地雷がいくつかあります。事前に把握しておきましょう。

  • exists の戻り値を盲信する (TOCTOU)Files.exists(p)true を取った直後に別のプロセスがファイルを消すと、続く Files.delete(p)NoSuchFileException を投げます。これは Time Of Check / Time Of Use の頭文字を取って TOCTOU 問題と呼ばれ、確実に避けたいケースでは Files.deleteIfExists(p) を使うか、例外を try で受け止めるのが定石です
  • Files.deleteFiles.deleteIfExists を混同する — 前者はファイルが無いと NoSuchFileException、後者は無くても静かに false を返します。「無いなら無いで OK」のときは後者、「絶対あるはず」のときは前者、と用途で使い分けましょう
  • Files.delete でディレクトリを消そうとして失敗する — 中身が残っているディレクトリは DirectoryNotEmptyException で消せません。再帰的に消したいときは Files.walk(p) で全ファイルを取り出してから逆順に Files.delete する、もしくは Files.walkFileTree を使うのが定石です

もう 1 つ大切なのは「削除は取り返しがつかない」という当たり前の事実です。rm -rf 相当のコードを書くときは、必ず開発環境で十分テストし、対象パスを誤って指定しないように二重三重のガードを入れましょう。

Files.delete は OS の API を直接呼ぶので、Recycle Bin (ゴミ箱) や Trash には入りません。一度消したら戻ってこないので、本番運用のスクリプトでは「事前にバックアップを取る」「ドライランモードを用意する」など慎重な実装を心掛けてください。

やってみよう

それでは今回の課題に挑戦してみましょう。やることは次のとおりです。

  1. Files.createTempFile("chot", ".tmp") などで一時ファイルを作り、Path を取得する
  2. Files.exists(p) の結果を boolean 変数 created に入れる (作った直後なので true のはず)
  3. Files.delete(p) で削除する
  4. もう一度 Files.exists(p) の結果を deleted に入れて、削除後は false になることを確認する
  5. "created=" + created + ",deleted=" + !deleted のように、最終的に "created=true,deleted=true" という文字列を組み立てて return する

ここで気をつけたいのは、戻り値に入れる deleted の意味です。テストが期待しているのは「削除されたか」つまり「削除後に存在しないこと」なので、!Files.exists(p) を入れます。手順 4 で得た値の論理を反転させて出力する点に注意してください。

メソッドの宣言には throws Exception (もしくは throws IOException) を付ける必要があります。Files.createTempFileFiles.delete がチェック例外を投げるためです。try-catch で受け止めても良いですが、コードを短く保つ意味では throws を付けるのが手軽です。

うまく動いたら、Files.createFile(Paths.get("work.tmp")) のように固定パスで作る版に書き換えてみたり、Files.deleteIfExists に置き換えて戻り値の違いを観察してみてください。Files クラスのメソッドを一通り触っておくと、後の章で出てくる読み書き操作も自然と理解できるようになります。

よくある質問

Q. 中級の内容は実務でどれくらい使いますか?

A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。

Q. Stream と for ループはどっちで書くべき?

A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。

Q. 次のステップでは何を学ぶべきですか?

A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。

次のレッスン

次は ファイル I/O まとめクイズ で、ファイル I/O まとめクイズ を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. Files.exists の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. Files.exists とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. Files.createTempFile (もしくは Files.createFile + Paths.get) で一時ファイルを作ること
  2. Files.exists(p) を 2 回呼び、作った直後は trueFiles.delete(p) 後は false になることを確認すること
  3. 最終的に "created=true,deleted=true" という形式の文字列を return すること

入出力例

test-cases.txt

createAndCheck()"created=true,deleted=true"

ヒント

main.java
main.java
学習モード

メモ

ファイルの存在確認と削除

⌘S で保存