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を使うと簡単に検証できます。

Java
public 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を使って入力を促し、不正ならもう一度呼び直します。

Java
public 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メソッドで呼び出す

Java
public 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で同様のバリデーションを行い、エラーメッセージをポップアップ表示する方法を紹介します。

参考資料