Java Tips | 基本ユーティリティ:ファイル存在確認

Java Java
スポンサーリンク

ファイル存在確認は「前提条件をちゃんと確かめてから動く」ための技

業務システムでは、「設定ファイルがある前提で起動する」「インポート用の CSV が届いている前提でバッチを回す」「テンプレートファイルがある前提で帳票を出力する」といった“前提条件”だらけです。
その前提が崩れているのに気づかず処理を進めると、途中で意味不明なエラーになったり、空の結果を出してしまったりします。

そこで重要になるのが「ファイル存在確認」です。
ただ if (file.exists()) と書くだけでなく、「ディレクトリじゃないか」「読めるか」「業務的に“存在しなかったらどうするか”」まで含めて、ユーティリティとして整理しておくと、実務でかなり効いてきます。


基本:File と Files.exists の違いを押さえる

まずは一番シンプルな File#exists

昔からあるやり方は java.io.File を使う方法です。

import java.io.File;

public class FileExistsBasic {

    public static void main(String[] args) {
        File file = new File("config/app.properties");
        if (file.exists()) {
            System.out.println("ファイルは存在します");
        } else {
            System.out.println("ファイルは存在しません");
        }
    }
}
Java

exists() は「そのパスに何か(ファイルかディレクトリか)が存在するか」を返します。
ただし、これだけだと「それがファイルなのかディレクトリなのか」「読めるのかどうか」は分かりません。

今どきは Path + Files.exists を使うのが基本

Java 7 以降では、java.nio.file.PathFiles を使うのが主流です。

import java.nio.file.Files;
import java.nio.file.Path;

public class NioFileExistsBasic {

    public static void main(String[] args) {
        Path path = Path.of("config/app.properties");
        if (Files.exists(path)) {
            System.out.println("ファイルは存在します");
        } else {
            System.out.println("ファイルは存在しません");
        }
    }
}
Java

Files.exists は、シンボリックリンクの扱いなども含めて、より新しいファイルシステム API と一緒に使えるので、
新規コードではこちらをベースに考えるのがおすすめです。


実務で使える「ファイル存在確認ユーティリティ」の最小形

「存在するか」だけでなく「通常ファイルか」まで見る

設定ファイルやテンプレートファイルなど、「普通のファイルであること」が前提のケースでは、
「存在するか」だけでなく「ディレクトリではないか」も確認したいです。

import java.nio.file.Files;
import java.nio.file.Path;

public final class FileChecks {

    private FileChecks() {}

    public static boolean exists(Path path) {
        return Files.exists(path);
    }

    public static boolean isRegularFile(Path path) {
        return Files.isRegularFile(path);
    }

    public static boolean isReadableFile(Path path) {
        return Files.isRegularFile(path) && Files.isReadable(path);
    }
}
Java

使う側はこう書けます。

Path config = Path.of("config/app.properties");
if (!FileChecks.isReadableFile(config)) {
    throw new IllegalStateException("設定ファイルが存在しないか、読み取りできません: " + config);
}
Java

ここで深掘りしたいポイントは三つです。

一つ目:業務的に必要な条件をメソッド名に乗せる
isReadableFile と書かれていれば、「存在していて、通常ファイルで、読み取り可能であること」が前提だと一目で分かります。
呼び出し側で exists && isRegularFile && isReadable と毎回書くより、意図がはっきりします。

二つ目:ディレクトリを弾く
Files.exists だけだと、同名のディレクトリがあっても true になります。
設定ファイルのつもりがディレクトリだった、という事故を防ぐためにも、isRegularFile を組み合わせるのが実務的です。

三つ目:権限(読み取り可能か)も一緒に見る
存在していても、権限がなくて読めないファイルは実質「使えない」ので、Files.isReadable も条件に含めておくと、エラーを早めに検知できます。


「必須ファイル」と「任意ファイル」で振る舞いを分ける

必須ファイルは「なければ即エラー」にする

例えば、「このアプリは config/app.properties がないと起動しても意味がない」というケースでは、
起動時にチェックして、なければ即エラーにしたほうが健全です。

public static void requireReadableFile(Path path, String description) {
    if (!isReadableFile(path)) {
        throw new IllegalStateException("必須ファイルが見つからないか読み取れません: " + description + " (" + path + ")");
    }
}
Java

使う側はこうです。

FileChecks.requireReadableFile(Path.of("config/app.properties"), "アプリケーション設定ファイル");
Java

ここでの重要ポイントは、「“必須かどうか”を if 文ではなくメソッド名で表現している」ことです。
requireReadableFile と書かれていれば、「ここで存在しなければ起動時に落ちる」という意図が一目で分かります。

任意ファイルは「なければスキップ」などの扱いにする

一方、「あれば使うけど、なければデフォルト動作でよい」という任意ファイルもあります。
その場合は、存在確認ユーティリティを使って分岐します。

Path optional = Path.of("config/feature-x.properties");
if (FileChecks.isReadableFile(optional)) {
    loadFeatureXConfig(optional);
} else {
    // ファイルがなければ機能 X を無効のままにする
}
Java

「必須/任意」の違いを、メソッドレベルと呼び出し側の分岐で明確に分けておくと、
後からコードを読んだときに「このファイルがどれくらい重要か」がすぐに理解できます。


例題:インポートバッチでのファイル存在確認

CSV インポート前に「ファイルがあるか」をチェックする

よくある業務バッチとして、「特定ディレクトリに置かれた CSV を読み込んで DB に取り込む」という処理を考えます。

import java.nio.file.Path;

public class CsvImportJob {

    public void run() {
        Path csv = Path.of("/data/import/users.csv");
        if (!FileChecks.isReadableFile(csv)) {
            // ログを出して終了(ジョブとしては失敗扱い)
            System.err.println("インポート対象の CSV が存在しません: " + csv);
            return;
        }

        // ここから先は「ファイルがある」前提で処理を書ける
        importUsers(csv);
    }

    private void importUsers(Path csv) {
        // CSV 読み込み処理…
    }
}
Java

ここでのポイントは、「前提条件を最初にチェックして、以降のコードをシンプルに保つ」ことです。
存在確認をあちこちでやるのではなく、「入口で一度だけチェックして、ダメなら早期リターン/例外」という形にすると、
後続の処理は「ファイルがある前提」で書けるので、コードが読みやすくなります。


ファイル存在確認の“落とし穴”も知っておく

存在確認と実際の利用の間に「時間差」がある

ファイルシステムは外部リソースなので、「存在確認したときにはあったけれど、実際に開こうとしたときには消えていた」ということが理論上あり得ます。
これは「TOCTOU(Time Of Check To Time Of Use)問題」と呼ばれる典型的なパターンです。

実務では、ローカルディスク上のファイルであればそこまで神経質になる必要はありませんが、
ネットワークドライブや他プロセスとの連携が絡む場合は、「存在確認に成功しても、開くときに失敗する可能性がある」前提で、
Files.newInputStream などの I/O 操作側でも例外処理を書いておくのが安全です。

「存在しない」こと自体が正常なケースもある

ログローテーション用のディレクトリや、一時ファイルの削除など、「存在しないならそれでよい」というケースもあります。
そういうときに、何でもかんでも「存在しない=エラー」としてしまうと、ログがノイズだらけになります。

ユーティリティを使うときは、「このファイルが存在しないのは“異常”なのか、“想定内”なのか」を意識して、
エラーにするか、警告にするか、何も出さないかを決めると、運用しやすいログになります。


まとめ:ファイル存在確認ユーティリティで身につけるべき感覚

ファイル存在確認は、「ただ true/false を知る」だけではなく、「そのファイルが業務的にどれくらい重要か」「どんな条件を満たしていれば“使える”とみなすか」を設計する行為です。

押さえておきたい感覚はこうです。

Files.exists だけでなく、Files.isRegularFileFiles.isReadable を組み合わせて、「使えるファイルかどうか」を判定する。
「必須ファイル」は requireReadableFile のようなメソッドで、存在しなければ起動時やジョブ開始時に即エラーにする。
「任意ファイル」は、存在しなければスキップやデフォルト動作に切り替えるなど、業務的な意味をコードに表現する。
存在確認は入口で一度だけ行い、以降の処理は「前提が満たされている」前提でシンプルに書く。
存在確認に成功しても、実際の I/O では失敗しうる前提で、読み込み側の例外処理もきちんと書く。

タイトルとURLをコピーしました