ファイル削除は「後片付けをちゃんとやる」ための技
業務システムは、動けば終わりではなく「後片付け」まで含めて仕事です。
一時ファイル、古いバックアップ、失敗した処理の中途半端な成果物など、放っておくとディスクを圧迫し、トラブルの原因になります。
だからこそ、「ファイル削除」をユーティリティとしてきちんと設計しておくと、
一時ファイルを確実に消したり、不要になった成果物を安全に片付けたりできるようになります。
ポイントは、「消すべきものだけを消す」「失敗したときにどう振る舞うかを決めておく」ことです。
基本:Files.delete と deleteIfExists の違い
delete は「なかったら例外」、deleteIfExists は「なければ何もしない」
Java 7 以降では、java.nio.file.Files を使ってファイルを削除します。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class DeleteBasic {
public static void main(String[] args) throws IOException {
Path path = Path.of("work/temp.txt");
Files.delete(path);
}
}
JavaFiles.delete は、「ファイル(または空ディレクトリ)が存在していれば削除する」「存在しなければ例外を投げる」という挙動です。
「絶対にあるはずのファイルがなかったら異常」とみなしたいときに向いています。
一方、「あってもなくてもよい、一応消しておきたい」という場面では deleteIfExists を使います。
Files.deleteIfExists(path);
Javaこちらは、「存在していれば削除する」「存在しなければ何もしない(例外も出さない)」という挙動です。
一時ファイルや、掃除バッチなど、「なければないで構わない」ものに向いています。
ここでの重要ポイントは、「“存在しないこと”をエラーにしたいかどうかで、delete と deleteIfExists を使い分ける」という感覚です。
実務で使える「安全なファイル削除ユーティリティ」の最小形
「失敗してもアプリは落とさない」safeDelete
一時ファイルの削除など、「消せなかったからといって業務処理を止めたくはない」場面が多くあります。
その場合は、「削除を試みるが、失敗してもログに出すだけ」というユーティリティが便利です。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public final class FileDeletes {
private FileDeletes() {}
public static void safeDelete(Path path) {
if (path == null) {
return;
}
try {
Files.deleteIfExists(path);
} catch (IOException e) {
System.err.println("ファイル削除に失敗しました: " + path + " : " + e.getMessage());
}
}
}
Java使う側はこう書きます。
Path temp = Path.of("work/tmp-12345.dat");
try {
// 一時ファイルを使った処理
} finally {
FileDeletes.safeDelete(temp);
}
Javaここで深掘りしたいのは、「try-finally と safeDelete の組み合わせで、“使ったら必ず片付ける”をコードとして表現している」ことです。
業務処理の成否に関わらず、finally で必ず削除が呼ばれ、削除に失敗してもアプリ全体は落ちません。
「後片付けはするが、片付け失敗で本体を巻き添えにしない」というバランスが実務的です。
「必ず消えていてほしい」場合のユーティリティ
消せなかったら異常として扱う requireDeleted
逆に、「このファイルが消せないなら、処理を続けると危険」というケースもあります。
例えば、「再実行時に同名ファイルがあると壊れる」「機密情報なので必ず削除されていなければならない」といった場面です。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public final class RequiredDeletes {
private RequiredDeletes() {}
public static void requireDeleted(Path path, String description) throws IOException {
if (path == null) {
return;
}
try {
Files.deleteIfExists(path);
} catch (IOException e) {
throw new IOException("必ず削除すべきファイルを削除できませんでした: " + description + " (" + path + ")", e);
}
}
}
Java使う側はこうです。
RequiredDeletes.requireDeleted(Path.of("work/secret.dat"), "機密データ一時ファイル");
Javaここでの重要ポイントは、「“消せなかったこと”を業務的な異常として扱う」ことです。
単に IOException を投げるのではなく、「何のためのファイルか」をメッセージに含めることで、ログを見たときに状況がすぐ分かります。
ディレクトリごと削除したい場合の考え方
Files.walk を使った再帰削除
Files.delete は「ファイル」か「空ディレクトリ」しか削除できません。
中身のあるディレクトリを丸ごと消したい場合は、自分で中身をたどって削除する必要があります。
import java.io.IOException;
import java.nio.file.*;
import java.util.Comparator;
public final class DirectoryDeletes {
private DirectoryDeletes() {}
public static void deleteTree(Path dir) throws IOException {
if (dir == null || !Files.exists(dir)) {
return;
}
Files.walk(dir)
.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.deleteIfExists(path);
} catch (IOException e) {
throw new RuntimeException("ディレクトリ削除中にエラー: " + path, e);
}
});
}
}
Java使う側はこうです。
DirectoryDeletes.deleteTree(Path.of("work/tmp-job-12345"));
Javaここで深掘りしたいのは、「削除順を“深い階層から先に”している」点です。Files.walk は親→子の順にパスを返すので、そのまま削除すると「中身があるディレクトリを先に消そうとして失敗」します。Comparator.reverseOrder() で逆順にソートすることで、「ファイル→子ディレクトリ→親ディレクトリ」の順に削除できるようにしています。
例題:ジョブごとの作業ディレクトリを丸ごと片付ける
「ジョブ開始時に作って、終了時に丸ごと消す」
バッチ処理などで、「ジョブごとに一時ディレクトリを作り、その中でファイルをやりくりする」という設計はよくあります。
その場合、「ジョブ終了時にディレクトリごと削除する」ユーティリティがあると便利です。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class JobRunner {
public void runJob() throws IOException {
Path workDir = Files.createTempDirectory("job-");
try {
// workDir 配下でいろいろなファイルを使う
Path temp = workDir.resolve("data.csv");
Files.writeString(temp, "...");
} finally {
DirectoryDeletes.deleteTree(workDir);
}
}
}
Javaここでのポイントは、「“ジョブのライフサイクル”と“作業ディレクトリのライフサイクル”を一致させている」ことです。
ジョブが終われば、そのジョブ専用のディレクトリも消えるので、長期的にゴミが溜まりません。
また、問題が起きたときに「一時的に削除をコメントアウトして中身を調査する」といった運用もしやすくなります。
ファイル削除の“落とし穴”と注意点
「消したつもりが消えていない」ケース
ファイル削除は、次のような理由で失敗することがあります。
同じファイルを別プロセスや別スレッドが開きっぱなし
権限が足りない
ネットワークドライブの一時的なエラー
これらは、Files.delete や deleteIfExists が IOException を投げることでしか分かりません。
だからこそ、「safeDelete ではログに出す」「requireDeleted では例外に包んで表に出す」といったポリシーを決めておくことが大事です。
「消してはいけないものを消してしまう」リスク
ファイル削除で一番怖いのは、「本当は残しておくべきファイルを誤って消してしまう」ことです。
これを防ぐために意識したいのは次のようなことです。
削除対象のパスを組み立てるロジックをユーティリティに閉じ込める
「どのディレクトリ配下だけを消すか」を明確に決める
ユーザー入力から直接パスを作って削除しない
特に、「ディレクトリごと削除する」ユーティリティは強力なので、「このディレクトリ配下だけ」という前提をしっかり決めておくと安全です。
まとめ:ファイル削除ユーティリティで身につけるべき感覚
ファイル削除は、「ただ消す」ではなく、「何を、いつ、どのくらいの強さで消すか」を設計する行為です。
押さえておきたい感覚はこうです。
「存在しないことをエラーにしたいかどうか」で、delete と deleteIfExists を使い分ける。
一時ファイルなどは、try-finally と safeDelete で「使ったら必ず片付ける」をコードにする。
「必ず消えていてほしい」ものは requireDeleted のようなメソッドで、消せなかったら異常として扱う。
ディレクトリ削除は Files.walk と逆順ソートで、「ファイル→子ディレクトリ→親ディレクトリ」の順に消す。
「消し忘れ」と「消しすぎ」の両方を意識し、削除対象と削除ポリシーをユーティリティに集約する。
