1. ファイルとディレクトリの存在確認メソッド
ファイル操作は時に地雷原を歩くようなものです。次のバイトの先に何が待っているか分かりません。朗報として、Java には「金属探知機」— ファイルやフォルダの存在をチェックするためのメソッド — があります。
File クラス: exists()、isFile()、isDirectory() による確認
古典的な方法は、クラス java.io.File を使うことです:
File file = new File("example.txt");
if (file.exists()) {
System.out.println("ファイルは存在します!");
} else {
System.out.println("ファイルが見つかりません。");
}
メソッド exists() は、その名前のファイルまたはフォルダが存在すれば true を返します。とはいえ、それだけでは不十分なこともあります。実在するのがファイルなのかディレクトリなのかを知りたい場合があります。
if (file.isFile()) {
System.out.println("これはファイルです。");
} else if (file.isDirectory()) {
System.out.println("これはディレクトリです。");
} else {
System.out.println("何も見つかりません。");
}
Path と Files クラス: モダンなアプローチ
近年のアプリケーションでは、より新しく強力な API java.nio.file を使うのがよいでしょう。ここでは存在確認に静的メソッド Files.exists() を使います:
import java.nio.file.*;
Path path = Paths.get("example.txt");
if (Files.exists(path)) {
System.out.println("NIOでファイルが見つかりました!");
}
種類を判定するには次を使います:
if (Files.isRegularFile(path)) {
System.out.println("これは通常のファイルです!");
}
if (Files.isDirectory(path)) {
System.out.println("これはディレクトリです!");
}
ヒント: 新規プロジェクトでは最初から NIO(Path、Files)を使うのがおすすめです。この API はよりモダンで機能が豊富で、try-with-resources とも相性が良いからです。
表: アプローチの比較
| 方法 | 存在確認 | 型の判定(ファイル/ディレクトリ) | 推奨度 |
|---|---|---|---|
|
可 | 可 (isFile(), isDirectory()) | 古い |
|
可 | 可 (isRegularFile(), isDirectory()) | 推奨 |
2. TOCTOU 問題: なぜ存在チェックは万能薬ではないのか
問題の本質は?
たとえば「ファイルは存在する!」と確認してすぐ読み取ろうとしたとします。しかし CPU の尺度では、その2つの操作の間に「永遠」とも言える時間が流れます。その間に、ファイルが削除・移動・別プロセスにより置き換えられたり、アクセス権が変更されたりするかもしれません。
これは、冷蔵庫を開けてケーキがあるのを確認し、扉を閉めてからもう一度開けたら、すでに食べられていた、という状況に似ています。プログラミングにもその「同居人」がいます。ほかのプロセスやユーザー、アンチウイルス、そしてファイルシステムそのものです。
なぜ重要か?
その結果、たとえファイルの存在を確認していても、開こうとした瞬間に例外が発生することがあります。たとえば FileNotFoundException や AccessDeniedException です。
つまり、チェックは保証ではなく、あくまで補助的な保険にすぎません。常に例外が起き得ることを念頭に置き、確実に処理しましょう。
3. 実践: 読み取り前にファイルの存在を確認する
存在すれば内容を出力し、なければユーザーに知らせる関数を追加します。古い API と新しい API の2通りを示します.
方法1: File を使う
import java.io.*;
public class FileExistenceCheck {
public static void main(String[] args) {
File file = new File("notes.txt");
if (file.exists() && file.isFile()) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("ファイルの読み取り中にエラーが発生しました: " + e.getMessage());
}
} else {
System.out.println("ファイル 'notes.txt' が見つかりません。");
}
}
}
方法2: Path と Files を使う
import java.nio.file.*;
import java.io.IOException;
public class PathExistenceCheck {
public static void main(String[] args) {
Path path = Paths.get("notes.txt");
if (Files.exists(path) && Files.isRegularFile(path)) {
try {
Files.lines(path).forEach(System.out::println);
} catch (IOException e) {
System.out.println("ファイルの読み取り中にエラーが発生しました: " + e.getMessage());
}
} else {
System.out.println("ファイル 'notes.txt' が見つかりません。");
}
}
}
チェック後でも必ず例外を捕捉する!
どちらの例でも、事前にチェックしているにもかかわらず、私たちは引き続き try–catch ブロックを使います。ファイルはいつでも消えたりアクセス不能になり得るからです。これはファイル操作の鉄則です!
4. ディレクトリの存在確認
同様に、ディレクトリの存在を確認し、なければ作成できます:
import java.nio.file.*;
public class DirectoryCheck {
public static void main(String[] args) {
Path dir = Paths.get("data");
if (Files.exists(dir) && Files.isDirectory(dir)) {
System.out.println("ディレクトリ 'data' が見つかりました。");
} else {
System.out.println("ディレクトリ 'data' が見つかりません。作成します...");
try {
Files.createDirectory(dir);
System.out.println("ディレクトリを作成しました!");
} catch (IOException e) {
System.out.println("ディレクトリの作成中にエラーが発生しました: " + e.getMessage());
}
}
}
}
ちなみに:
メソッド Files.createDirectory() は、ディレクトリが既にある場合は例外を投げます。ディレクトリ階層(例: "data/2025/09")を作成したい場合は、一部が既存でもエラーにしない Files.createDirectories() を使いましょう。
5. 注意点: 相対パスと絶対パス
相対パス
"notes.txt" と書いた場合、プログラムは「現在の作業ディレクトリ」を探します。それがどこかは、アプリをどのように・どこから起動するか(IDE、ターミナル、JAR をダブルクリック等)によって変わります。
絶対パス
確実に場所を指定したい場合は、絶対パスを使うか動的に組み立てましょう:
String userHome = System.getProperty("user.home");
Path filePath = Paths.get(userHome, "myapp", "notes.txt");
オブジェクトの種類のチェック
「ファイル」が実はディレクトリということもあります。存在だけでなく種類も確認しましょう:
if (Files.isRegularFile(path)) {
// これはファイルです!
}
if (Files.isDirectory(path)) {
// これはディレクトリです!
}
6. TOCTOU 問題のデモ
存在を確認した後、開く前にファイルが消える状況を再現してみましょう。コードを実行し、チェックと読み取りの間に手動でファイルを削除してください:
import java.io.*;
import java.nio.file.*;
public class TOCTOUExample {
public static void main(String[] args) {
Path path = Paths.get("notes.txt");
if (Files.exists(path)) {
System.out.println("ファイルが見つかりました。これから読み込みます...");
// この時点でエクスプローラーを開き、"notes.txt" を手動で削除してください!
try {
Files.lines(path).forEach(System.out::println);
} catch (IOException e) {
System.out.println("おっと!ファイルが消えました: " + e.getMessage());
}
} else {
System.out.println("ファイルが見つかりません。");
}
}
}
結果:
チェックと読み取りの間に削除できれば、例外が発生します。これは、どれほど慎重にチェックしても、予期しない変更からは守れないことを明確に示します。
7. ファイル/ディレクトリの存在確認でありがちなミス
誤り1: チェックだけに頼み、 try–catch を使わない。 「ファイルはある」と分かったからといって安心して読み込める、と初心者は考えがちです。しかし、ファイルは消えたりアクセス不能になったりし、プログラムがクラッシュすることがあります。
誤り2: 存在だけを確認し、種類を確認しない。 exists() だけを確認して、ファイルかディレクトリかを特定しないと、ディレクトリをファイルとして開こうとしてエラーになります.
誤り3: 作業ディレクトリを理解せずに相対パスを使う。 プログラムが「別の場所」を探してしまい、ユーザーはなぜ動かないのか分かりません。
誤り4: アクセス権を考慮しない。 ファイルやディレクトリは存在していても、読み書き権限がない場合があります。こうした問題は開こうとしたときに初めて表面化するので、常に try–catch を使いましょう。
誤り5: OS ごとの大文字小文字の扱いを考慮しない。 Windows ではファイル名は大文字小文字を区別しませんが、Linux では区別します。"Notes.txt" を探しているのに "notes.txt" を見つけられない、といったことが起こり得ます。
GO TO FULL VERSION