Java Tips | 基本ユーティリティ:拡張子取得

Java Java
スポンサーリンク
スポンサーリンク

拡張子取得は「ファイルの中身を推測するためのラベル取り」

業務システムでは、「CSV だけ受け付けたい」「画像ファイルだけ処理したい」「ログローテーションで .log だけ対象にしたい」など、
「ファイル名の拡張子を見て処理を分ける」場面が本当に多いです。

ここで雑に文字列を切り出すと、a.b.c.csv や .gitignore、no_extension のような“ちょっと変わった名前”で簡単にバグります。
だからこそ、「拡張子取得」をユーティリティとしてきちんと設計しておくと、
判定ロジックが一箇所にまとまり、実務での事故をかなり減らせます。


基本:最後の「.」以降を拡張子とみなす

文字列から拡張子を切り出す最小パターン

まずは、ファイル名(またはパス)から拡張子を取り出す、いちばん素朴な実装を見てみます。

public final class Extensions {

    private Extensions() {}

    public static String getExtension(String filename) {
        if (filename == null || filename.isEmpty()) {
            return "";
        }
        int lastSeparator = Math.max(filename.lastIndexOf('/'), filename.lastIndexOf('\\'));
        String name = filename.substring(lastSeparator + 1); // ディレクトリ部分を除く

        int dot = name.lastIndexOf('.');
        if (dot <= 0 || dot == name.length() - 1) {
            // 先頭がドット(.gitignore)や、ドットなし、末尾がドット("name.")は「拡張子なし」と扱う
            return "";
        }
        return name.substring(dot + 1);
    }
}
Java

使い方はこうです。

System.out.println(Extensions.getExtension("report.csv"));          // csv
System.out.println(Extensions.getExtension("a.b.c.csv"));          // csv
System.out.println(Extensions.getExtension("/var/log/app.log"));   // log
System.out.println(Extensions.getExtension(".gitignore"));         // ""
System.out.println(Extensions.getExtension("no_extension"));       // ""
System.out.println(Extensions.getExtension("trailing."));          // ""
Java

ここで重要なのは、次のような“微妙なケース”をちゃんと扱っていることです。

  • 先頭ドットのファイル(.gitignore)は「隠しファイル」であって、拡張子とはみなさない。** Baeldung**
  • ドットがないファイル名(no_extension)は拡張子なし。
  • 最後がドットで終わる(name.)のも拡張子なし扱いにする。

「最後のドット以降が拡張子」というルール自体はシンプルですが、
「どこまでを“拡張子あり”とみなすか」をきちんと決めておくことが、実務ではとても大事です。


Path と組み合わせて「パスから拡張子」を取る

まずファイル名部分だけを取り出してから拡張子を取る

java.nio.file.Path を使っているコードなら、Path からファイル名部分を取り出してから、先ほどのロジックを使うときれいです。

import java.nio.file.Path;

public final class PathExtensions {

    private PathExtensions() {}

    public static String getExtension(Path path) {
        if (path == null) {
            return "";
        }
        Path fileName = path.getFileName();
        if (fileName == null) {
            return "";
        }
        return Extensions.getExtension(fileName.toString());
    }
}
Java

使い方はこうです。

Path p1 = Path.of("/var/log/app.log");
System.out.println(PathExtensions.getExtension(p1)); // log

Path p2 = Path.of(".gitignore");
System.out.println(PathExtensions.getExtension(p2)); // ""
Java

ここでのポイントは、「ディレクトリ部分は Path に任せて、拡張子判定は“ファイル名だけ”に集中させる」ことです。
区切り文字(/ と \)を自分で気にしなくてよくなり、拡張子ロジックもシンプルに保てます。


例題:拡張子で処理を振り分ける

CSV と JSON で処理を変える

よくあるパターンとして、「同じディレクトリに CSV と JSON が混在していて、拡張子で処理を変える」というケースを考えます。

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

public class ImportJob {

    public void run(Path file) throws Exception {
        String ext = PathExtensions.getExtension(file).toLowerCase();

        switch (ext) {
            case "csv" -> importCsv(file);
            case "json" -> importJson(file);
            default -> throw new IllegalArgumentException(
                    "サポートされていない拡張子です: " + ext + " (" + file + ")");
        }
    }

    private void importCsv(Path file) throws Exception {
        System.out.println("CSV として処理: " + file);
        // CSV 読み込み処理…
    }

    private void importJson(Path file) throws Exception {
        System.out.println("JSON として処理: " + file);
        // JSON 読み込み処理…
    }
}
Java

ここで深掘りしたいのは、「拡張子取得ユーティリティのおかげで、“判定ロジック”がこのクラスから消えている」ことです。
lastIndexOf('.') などの細かい処理は Extensions に閉じ込めてしまい、
ここでは「拡張子が何か」にだけ集中して処理を分けています。

この分離ができていると、「拡張子の扱いを変えたい(例:.tar.gz を特別扱いしたい)」ときも、
ユーティリティ側だけを直せばよくなり、業務ロジックを触らずに済みます。


例題:アップロード時に「許可された拡張子」だけ受け付ける

拡張子ホワイトリストで簡易バリデーション

ファイルアップロード機能では、「画像だけ」「CSV だけ」といった制限をかけることが多いです。
その第一段階として、「拡張子でフィルタする」ユーティリティを用意しておくと便利です。

import java.nio.file.Path;
import java.util.Set;

public final class UploadExtensions {

    private UploadExtensions() {}

    private static final Set<String> ALLOWED_IMAGE_EXT = Set.of("png", "jpg", "jpeg", "gif");

    public static void validateImage(Path uploadedFile) {
        String ext = PathExtensions.getExtension(uploadedFile).toLowerCase();
        if (!ALLOWED_IMAGE_EXT.contains(ext)) {
            throw new IllegalArgumentException(
                    "画像として許可されていない拡張子です: " + ext + " (" + uploadedFile + ")");
        }
    }
}
Java

使い方はこうです。

Path uploaded = Path.of("work/upload/user-icon.PNG");
UploadExtensions.validateImage(uploaded); // 小文字化しているので png として扱われる
Java

ここでの重要ポイントは、「拡張子を小文字にそろえてから判定している」ことです。
PNG と png を別物として扱うと、ユーザーにとっては理不尽な挙動になります。
toLowerCase() で正規化してからホワイトリストと比較するのが、実務ではほぼ必須のパターンです。

もちろん、本気のセキュリティ対策としては「中身の MIME タイプを検査する」などが必要ですが、
「拡張子チェック」はその前段として、安価に弾けるものを弾くフィルタとして有効です。


既存ライブラリ(Apache Commons IO など)を使うかどうか

FilenameUtils.getExtension の存在を知っておく

Apache Commons IO には、FilenameUtils.getExtension(String) という便利メソッドがあります。

import org.apache.commons.io.FilenameUtils;

public class CommonsExample {

    public static void main(String[] args) {
        String ext = FilenameUtils.getExtension("student-records.pdf");
        System.out.println(ext); // pdf
    }
}
Java

既に Commons IO を使っているプロジェクトなら、これをそのまま使うのも十分アリです。
ただし、「ライブラリに依存するか」「自前ユーティリティで完結させるか」はプロジェクト方針次第なので、
チームで方針を決めておくとよいです。

個人的には、「拡張子取得くらいなら自前ユーティリティで十分」「他にも Commons IO を使うなら乗っかる」というバランスが取りやすいと思っています。


拡張子取得の“落とし穴”と注意点

拡張子は「中身の保証」ではない

一番大事な注意点は、「拡張子は“中身の種類”を保証してくれない」ということです。
virus.exe を virus.png にリネームすることは簡単にできますし、
.csv と名乗っているけれど実際は全然違う形式、ということもあり得ます。

拡張子でできるのは、あくまで「ざっくりとした振り分け」や「ユーザーへのフィードバック」までです。
本当に安全性や形式を保証したいなら、MIME タイプや中身のパースで検証する必要があります。

「複合拡張子」をどう扱うかを決めておく

backup.tar.gz のように、「ドットが複数ある拡張子」をどう扱うかも設計ポイントです。
今回の実装では「最後のドット以降(gz)だけ」を拡張子とみなしていますが、
業務によっては「tar.gz というセットで扱いたい」こともあります。

その場合は、ユーティリティ側で特別扱いするか、
「まず gz として判定し、必要ならファイル名全体で追加チェックする」といった二段構えにするなど、
プロジェクトとしてのルールを決めておくと、後から迷いません。


まとめ:拡張子取得ユーティリティで身につけたい感覚

拡張子取得は、「ただ文字列を切り出す」ではなく、「現実世界の“ちょっと変なファイル名”も含めて安全に扱う」ための技です。

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

  • 先頭ドット(.gitignore)、ドットなし、末尾ドットなどのケースをどう扱うかを決め、ユーティリティに閉じ込める。
  • Path からは getFileName() でファイル名だけを取り出し、拡張子判定ロジックをシンプルに保つ。
  • 拡張子は小文字にそろえてからホワイトリスト判定し、「csv と CSV を同じもの」として扱う。
  • 拡張子はあくまで“ラベル”であり、中身の保証ではないことを理解したうえで、バリデーションの一部として使う。
  • 必要に応じて Commons IO などの既存ライブラリも検討しつつ、「プロジェクトとしてどこまで自前でやるか」を決めておく。

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