markdown
はじめに (対象読者・この記事でわかること)
この記事は、Javaでコンソールアプリケーションを作りたての初学者〜中級者向けです。
「ユーザーに誕生日を入力してもらいたいけど、数字以外や変な日付(13月32日など)を弾きたい」という悩みを抱えている方を想定しています。
記事を読み終えると、以下のことができるようになります。
- 正規表現で「◯月◯日」形式を簡単に判定する方法
- 例外処理(
try-catch)を使ってプログラムをクラッシュさせない方法 - 再帰呼び出しで入力を何度でもやり直させる方法
前提知識
- Javaの基本文法(変数・メソッド・クラス)がなんとなく書ける
- 標準入力(
Scannerクラス)を使ったことがある - 正規表現の存在を聞いたことがある(細かい構文は説明します)
なぜ「誕生日入力」は難しいのか
コンソールアプリで「誕生日を入力してください」と表示すると、ユーザーは以下のように入力してきます。
3月15日
3/15
3-15
March 15
3月31日(全角)
要件として「◯月◯日」の半角数字+月・日に限定したい場合、単純にScanner#next()やnextLine()で受け取るだけでは不十分です。
かつ、存在しない日付(2月30日や13月1日)も排除したい。
つまり「形式チェック」と「論理チェック」の2段構えが必要になります。
ステップバイステップでガードする実装
ステップ1:正規表現で形式をチェックする
まず「◯月◯日」の形式だけを受け付ける正規表現を書きます。
String#matches()を使えば一行で判定できます。
Java// 半角数字1〜2桁 + 月 + 半角数字1〜2桁 + 日 static final Pattern DATE_PATTERN = Pattern.compile("^([1-9]|1[0-2])月([1-9]|[12][0-9]|3[01])日$"); public static boolean isValidFormat(String input) { return DATE_PATTERN.matcher(input).matches(); }
ポイント
- ([1-9]|1[0-2])で1〜12月を表現
- ([1-9]|[12][0-9]|3[01])で1〜31日を表現
- 先頭^・末尾$を忘れると「abc3月15日xyz」も通ってしまう
ステップ2:存在する日付かどうかをチェックする
形式が正しくても「4月31日」は無効です。
java.time.MonthDayを使うと簡単に検証できます。
Javapublic static boolean isExistDate(String input) { // まずキャプチャグループで月・日を取り出す Matcher m = DATE_PATTERN.matcher(input); if (!m.matches()) return false; int month = Integer.parseInt(m.group(1)); int day = Integer.parseInt(m.group(2)); try { MonthDay.of(month, day); // 存在しなければDateTimeException return true; } catch (DateTimeException ex) { return false; } }
ステップ3:入力ループを再帰呼び出しで実装
Scannerを使って入力を促し、不正ならもう一度呼び直します。
Javapublic static String askBirthDay(Scanner sc) { System.out.print("誕生日を「◯月◯日」で入力してください > "); String line = sc.nextLine().trim(); if (!isValidFormat(line)) { System.out.println("形式が違います。半角数字で「3月15日」のように入力してください。"); return askBirthDay(sc); // 再帰 } if (!isExistDate(line)) { System.out.println("存在しない日付です。もう一度入力してください。"); return askBirthDay(sc); // 再帰 } return line; }
再帰のメリット
- ローカル変数だけで済み、スタックを消費するが入力ミスは通常数回程度
- while(true)を書かなくても構造がシンプル
ステップ4:mainメソッドで呼び出す
Javapublic static void main(String[] args) { Scanner sc = new Scanner(System.in); String birthDay = askBirthDay(sc); System.out.println("あなたの誕生日は " + birthDay + " ですね!"); }
実行例
誕生日を「◯月◯日」で入力してください > 13月1日
存在しない日付です。もう一度入力してください。
誕生日を「◯月◯日」で入力してください > 2月30日
存在しない日付です。もう一度入力してください。
誕生日を「◯月◯日」で入力してください > 2月29日
あなたの誕生日は 2月29日 ですね!
ハマった点と解決策
ハマりポイント1:正規表現で全角数字を見逃した
「3月15日」が通ってしまう。
→ 最初にString#replaceAll("[0-9]", "0-9")で全角を半角に正規化してからチェックするか、正規表現に全角を含める。
ハマりポイント2:Scannerの残滓バッファ
nextInt()後にnextLine()を呼ぶと空文字が返ってくる。
→ 統一してnextLine()だけを使い、手動でInteger.parseInt()するか、再帰構造を採用して余分な読み飛ばしを回避。
ハマりポイント3:再帰が無限に続く
悪意のあるユーザーが何千回も間違え続けるとStackOverflowError。
→ 再帰深度をカウントし、一定回数(例:100回)を超えたら例外を投げて終了する保険を入れる。
まとめ
本記事では、Javaで「誕生日(◯月◯日)」形式の入力を正確に制御する方法を解説しました。
- 正規表現で形式をガードするとコードが短くて高速
java.time.MonthDayを使えば存在する日付かの論理チェックが楽- 再帰呼び出しで入力ループをシンプルに保てる
これでユーザーからの変な入力も安心して受け付けられます。
次回は、SwingやJavaFXで同様のバリデーションを行い、エラーメッセージをポップアップ表示する方法を紹介します。
参考資料
