java-recipes

ホーム 入力値バリデーション › V-01

V-01: 数値・文字列・日付のバリデーション

ユーザーが入力した値を検証する基本パターンを解説します。 数値変換の例外処理、正規表現によるフォーマットチェック、日付の妥当性確認、そして複数エラーを一括で集約する方法を学びましょう。

いつ使うか

  • Web フォームから受け取った文字列が正しい形式かチェックするとき
  • CSV や Excel から読み込んだデータを処理する前に品質チェックするとき
  • REST API で受け取ったリクエストパラメータを検証するとき
  • 設定ファイルの値が期待する範囲内かチェックするとき

バリデーションを行うべき場所(システム境界)

バリデーションは「システムの外からデータが入ってくる場所」で必ず行います。 信頼できないデータをそのまま処理すると、予期しない例外や誤った計算結果を引き起こします。

場所具体例
ユーザー入力受け付け時フォーム送信、コマンドライン引数
API リクエスト受信時REST API のリクエストボディ、クエリパラメータ
外部ファイル読み込み時CSV・JSON・Excel ファイルのパース後

サンプルコード

ValidationSample.java
import java.util.*;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.regex.*;

public class ValidationSample {

    // 数値バリデーション: 文字列が整数かチェック
    public static boolean isInteger(String value) {
        if (value == null || value.isEmpty()) {
            return false;
        }
        try {
            Integer.parseInt(value);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    // 数値バリデーション: 範囲チェック付き
    public static List<String> validateAge(String value) {
        List<String> errors = new ArrayList<>();
        if (value == null || value.isEmpty()) {
            errors.add("年齢は必須です");
            return errors;
        }
        try {
            int age = Integer.parseInt(value);
            if (age < 0) {
                errors.add("年齢は0以上を入力してください");
            }
            if (age > 150) {
                errors.add("年齢は150以下を入力してください");
            }
        } catch (NumberFormatException e) {
            errors.add("年齢は数値で入力してください");
        }
        return errors;
    }

    // 文字列バリデーション: メールアドレス形式チェック
    private static final Pattern EMAIL_PATTERN =
        Pattern.compile("^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$");

    public static boolean isValidEmail(String email) {
        if (email == null || email.isEmpty()) {
            return false;
        }
        return EMAIL_PATTERN.matcher(email).matches();
    }

    // 文字列バリデーション: 電話番号(日本)
    private static final Pattern PHONE_PATTERN =
        Pattern.compile("^(0[0-9]{1,4}-[0-9]{1,4}-[0-9]{4}|0[0-9]{9,10})$");

    public static boolean isValidPhone(String phone) {
        if (phone == null || phone.isEmpty()) {
            return false;
        }
        return PHONE_PATTERN.matcher(phone).matches();
    }

    // 日付バリデーション: ISO形式(yyyy-MM-dd)
    public static boolean isValidDate(String dateStr) {
        if (dateStr == null || dateStr.isEmpty()) {
            return false;
        }
        try {
            LocalDate.parse(dateStr); // DateTimeParseException が発生しなければ有効
            return true;
        } catch (DateTimeParseException e) {
            return false;
        }
    }

    // 複数バリデーションをまとめて実行してエラーを集約する
    public static List<String> validateUserInput(
            String name, String email, String ageStr, String birthDate) {
        List<String> errors = new ArrayList<>();

        // 名前チェック
        if (name == null || name.trim().isEmpty()) {
            errors.add("名前は必須です");
        } else if (name.length() > 50) {
            errors.add("名前は50文字以内で入力してください");
        }

        // メールチェック
        if (!isValidEmail(email)) {
            errors.add("メールアドレスの形式が正しくありません");
        }

        // 年齢チェック(複数エラーを集約)
        errors.addAll(validateAge(ageStr));

        // 生年月日チェック
        if (!isValidDate(birthDate)) {
            errors.add("生年月日は yyyy-MM-dd 形式で入力してください(例: 1990-01-15)");
        }

        return errors;
    }

    public static void main(String[] args) {
        System.out.println("=== 数値バリデーション ===");
        System.out.println(isInteger("123"));    // true
        System.out.println(isInteger("12.3"));   // false(小数は false)
        System.out.println(isInteger("abc"));    // false
        System.out.println(isInteger(""));       // false

        System.out.println("\n=== メールバリデーション ===");
        System.out.println(isValidEmail("user@example.com")); // true
        System.out.println(isValidEmail("user@example"));     // false
        System.out.println(isValidEmail("invalid"));          // false

        System.out.println("\n=== 日付バリデーション ===");
        System.out.println(isValidDate("2024-03-15")); // true
        System.out.println(isValidDate("2024-13-01")); // false (13月)
        System.out.println(isValidDate("2024/03/15")); // false (形式違い)

        System.out.println("\n=== 複合バリデーション ===");
        List<String> errors = validateUserInput("", "invalid-email", "abc", "2024-13-01");
        if (errors.isEmpty()) {
            System.out.println("バリデーション OK");
        } else {
            System.out.println("エラー " + errors.size() + " 件:");
            for (String error : errors) {
                System.out.println("  - " + error);
            }
        }
    }
}

Java 8 では isEmpty() で空文字を確認し、スペースのみの入力は trim().isEmpty() で確認します。NumberFormatException・DateTimeParseException を catch して、ユーザーへの分かりやすいメッセージに変換しています。

よくあるミス・注意点

例外はユーザーへの分かりやすいメッセージに変換する

Integer.parseInt() で例外が発生してもそのまま伝播させず、 「年齢は数値で入力してください」のようなユーザーが理解できるメッセージに変換して返しましょう。 スタックトレースをそのまま画面に表示するのは避けてください。

isEmpty() と isBlank() の違いに注意する

isEmpty() は空文字(長さ0)のみを検出します。スペースだけの入力 "   " は isEmpty() では検出できません。 Java 11 以降は isBlank() を、Java 8 では trim().isEmpty() を使いましょう。

メールアドレスの完全な検証は正規表現だけでは不可能

正規表現はメールアドレスの「形式」を確認するだけです。 そのアドレスが実際に存在するかどうかは確認できません。 実務では形式チェックの後に確認メールを送信する「メール認証」を組み合わせるのが標準的な方法です。

複数のエラーは1つ見つかったら即 return せず、全エラーを集約する

バリデーションエラーが複数ある場合、最初のエラーだけ表示して終わると、 ユーザーは修正するたびに次のエラーが出て何度もフォームを送り直すことになります。 全てのエラーをリストに集約して一度に返すことで UX が改善します。

テストする観点

  • 正常値(有効な整数・メールアドレス・日付)が true を返すこと
  • 空文字("")と null が false またはエラーを返すこと(境界値)
  • スペースのみ(" ")が false またはエラーを返すこと
  • 年齢の境界値(0、150、-1、151)が正しく検証されること
  • メールアドレスの形式違い(user@example@example.com)が false を返すこと
  • 存在しない日付(2024-13-01、2024-02-30)が false を返すこと
  • 複合バリデーションで全てのエラーが集約されて返ること

GitHub でソースコードを見る →