拡張子取得は「ファイルの中身を推測するためのラベル取り」
業務システムでは、「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 などの既存ライブラリも検討しつつ、「プロジェクトとしてどこまで自前でやるか」を決めておく。
