ファイルの存在確認と削除
ファイルの存在確認と削除 とは
Files.exists / Files.delete などの基本 API を使い、一時ファイルを作って存在を確認し、削除してからもう一度確認するコードを書いてみよう。
ファイルが「ある」「ない」を確かめる
業務アプリでも個人ツールでも、ファイルを扱うコードを書いていると「指定したパスにファイルがあるかどうか確かめたい」「使い終わったら確実に消したい」という場面に必ず出会います。Java では java.nio.file.Files というユーティリティクラスがこの手の操作を一手に引き受けてくれます。本章では Files.exists Files.createFile Files.delete を中心に、ファイルの存在確認と後片付けの作法を学んでいきましょう。
Java の世界には古くは
java.io.Fileという API がありましたが、現代のコードではjava.nio.file.Pathとjava.nio.file.Filesの組み合わせを使うのが標準です。例外の扱いやシンボリックリンク対応がしっかりしており、WindowsとLinuxの差異も吸収してくれます。
Path はファイルやディレクトリの「位置」を表す抽象化で、Files クラスは Path を受け取って実際の操作を行います。後者は static メソッドの集まりなので、new Files() のようにインスタンス化することはありません。Files.exists(p) Files.delete(p) のように、Files に Path を渡すスタイルで使います。
Files.exists と仲間たち
まず存在確認系の API を整理します。よく使うのは次の 4 つです。
Files.exists(p)— ファイルやディレクトリが存在すればtrueFiles.notExists(p)— 存在しないと確認できればtrueFiles.isRegularFile(p)— 通常のファイル (ディレクトリやリンクではない) ならtrueFiles.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.createFile と Files.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.createFile も Files.delete も IOException を投げ得るので、メソッドのシグネチャには 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 を使うと、存在チェックと削除をひとつにまとめられるので、レースコンディションを避けやすくなります。
よくある間違い
ファイル操作は OS との境界線にあるため、初学者が踏みやすい地雷がいくつかあります。事前に把握しておきましょう。
existsの戻り値を盲信する (TOCTOU) —Files.exists(p)でtrueを取った直後に別のプロセスがファイルを消すと、続くFiles.delete(p)がNoSuchFileExceptionを投げます。これは Time Of Check / Time Of Use の頭文字を取ってTOCTOU問題と呼ばれ、確実に避けたいケースではFiles.deleteIfExists(p)を使うか、例外をtryで受け止めるのが定石ですFiles.deleteとFiles.deleteIfExistsを混同する — 前者はファイルが無いとNoSuchFileException、後者は無くても静かにfalseを返します。「無いなら無いで OK」のときは後者、「絶対あるはず」のときは前者、と用途で使い分けましょうFiles.deleteでディレクトリを消そうとして失敗する — 中身が残っているディレクトリはDirectoryNotEmptyExceptionで消せません。再帰的に消したいときはFiles.walk(p)で全ファイルを取り出してから逆順にFiles.deleteする、もしくはFiles.walkFileTreeを使うのが定石です
もう 1 つ大切なのは「削除は取り返しがつかない」という当たり前の事実です。rm -rf 相当のコードを書くときは、必ず開発環境で十分テストし、対象パスを誤って指定しないように二重三重のガードを入れましょう。
Files.deleteは OS の API を直接呼ぶので、Recycle Bin(ゴミ箱) やTrashには入りません。一度消したら戻ってこないので、本番運用のスクリプトでは「事前にバックアップを取る」「ドライランモードを用意する」など慎重な実装を心掛けてください。
やってみよう
それでは今回の課題に挑戦してみましょう。やることは次のとおりです。
Files.createTempFile("chot", ".tmp")などで一時ファイルを作り、Pathを取得するFiles.exists(p)の結果をboolean変数createdに入れる (作った直後なのでtrueのはず)Files.delete(p)で削除する- もう一度
Files.exists(p)の結果をdeletedに入れて、削除後はfalseになることを確認する "created=" + created + ",deleted=" + !deletedのように、最終的に"created=true,deleted=true"という文字列を組み立ててreturnする
ここで気をつけたいのは、戻り値に入れる deleted の意味です。テストが期待しているのは「削除されたか」つまり「削除後に存在しないこと」なので、!Files.exists(p) を入れます。手順 4 で得た値の論理を反転させて出力する点に注意してください。
メソッドの宣言には throws Exception (もしくは throws IOException) を付ける必要があります。Files.createTempFile や Files.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 つができることを確認しましょう。
- Files.exists の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. Files.exists とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
Files.createTempFile(もしくはFiles.createFile+Paths.get) で一時ファイルを作ることFiles.exists(p)を 2 回呼び、作った直後はtrue、Files.delete(p)後はfalseになることを確認すること- 最終的に
"created=true,deleted=true"という形式の文字列をreturnすること
入出力例
test-cases.txt
createAndCheck() → "created=true,deleted=true"