※この章は研修時間の都合により丸ごとスキップすることがあります。

前回は、 JDBCでデータベースと接続する方法について学びました。今回は、正規表現を使ったバリデーションについて学びます。

1. なぜ、正規表現を使ったバリデーションが必要なのか?

皆さんがユーザー登録をする際、メールアドレスを入力したことがあるのではないでしょうか?その際、入力したメールアドレスが一定の形式に従っていない場合(例えが@が無いなど)はエラーが表示されましたね。

入力されたデータが、あらかじめ規定された形式などに適合しているかどうかをチェックする仕組みがバリデーション【validation】です。

さらにバリデーションをするためには正規表現【Regular Expressions】が便利です。

正規表現とは、「複数のパターンがありえる文字列を一つの文字列で表現する方法」です。

例えば、日本の郵便番号は「NNN-NNNN」という形式なので理論上は10,000,000パターンあります。しかし、正規表現では、「^[0-9]{3}-[0-9]{4}$」という一種類の文字列で表現できます。

正規表現の使われる領域としてはプログラム言語だけでなくIDEやエディタの検索/置換機能、Excelの関数、さらにはLinuxなどのOSの機能にも組み込まれています。ぜひともマスターしたい知識です。ただし、正規表現に関する知識は膨大で、それだけで1冊の本になるくらいです。ですから、ここでは当社の新人エンジニア研修の最終課題をクリアするための正規表現に絞って解説しようと思います。

ひとまずのゴールとして以下のメールアドレスのルール判定をする正規表現を考えてみます。

メールアドレスの形式はユーザー名@ドメイン名
ユーザー名に使用できる文字は半角英小文字、数字、「-」(ハイフン)、「.」(ドット)、「_」(アンダーバー)のみ
ドメイン名の区切り文字は 「.」(ドット) で複数使用してもよい。ドメイン名の最初の1文字は半角英数字、2文字目以降は半角英数字または「-」(ハイフン)または 「.」(ドット) ただし、ハイフンで終わらない。
ドメイン名の最後はアルファベット2文字以上で終わる

下図9.1にメールアドレスの例を示します。

図9.1 メールアドレスの例

以下がメールアドレスが正しいルールに則っているかどうかの正規表現なのですが、これを理解できることをゴールとします。

※厳密に言えば運営者の裁量により上記ルールに沿わないメールアドレスも発行可能なため、メールアドレスのバリデーションはHTMLの<input type="email">に任せ、実際に入力されたメールアドレスに対して送信してみて、返信を待つのが妥当な方法です。(ダブルオプトインといいます)

1.1 正規表現の使い方

以下のサンプルプログラムを見て質問に答えてください。

package p09;

import java.util.Scanner;
import java.util.regex.Pattern;

public class Validator {

    public static void main(String[] args) {

        String rule = "メールアドレス";
        String regex = "^[a-z0-9-._]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\\.)+[a-zA-Z]{2,}$";

        Validator.verify(rule, regex);
    }

    public static void verify(String rule, String regex) {
        String input;
        Boolean ok;
        try (Scanner scanner = new Scanner(System.in)) {
            while (true) {
                System.out.println("設定してください:" + rule);
                input = scanner.next();
                ok = Pattern.matches(regex, input);
                if (ok) {
                    System.out.println("ルール通りに設定されました。");
                    System.out.println("入力内容:" + input);
                    System.out.println("ルール:" + rule);
                } else {
                    System.err.println("ルール通りに設定してください。");
                }
                System.out.println();
            }
        }
    }
}
  • このプログラムにフィールドとメソッドはそれぞれいくつありますか?
あなたの答え:
  • verifyメソッドについて何が言えますか?(引数、戻り値、可視性など)
あなたの答え:
  • このプログラムにローカル変数はいくつあり、それぞれの役割は何でしょうか?
あなたの答え:
  • このプログラムは何をしたら終了しますか?
あなたの答え:

みなさんのメールアドレスやでたらめなアドレスをいくつか試してみていただきたいと思います。

プログラムは正しく判定しましたか?


ここでのポイントは以下の24行目の部分です。

Pattern.matches(regex, input);

Patternクラスのstaticメソッド matchesメソッドを使い、正規表現のルールに沿っているかを判定します。matchesメソッド の引数はそれぞれ正規表現と比較対象文字列です。また、戻り値はboolean値です。

では、ここからステップバイステップで上記メールアドレスの正規表現が理解できるようになりましょう。

特定の1文字との一致

先のメールアドレスの正規表現を主に後ろから分解して解読してゆきましょう。

まずは、特定の1文字との一致です。先のプログラムの10,11行目を以下のように書き換えます。(以降同様)

参照:Validator1.java

String rule = "ルール:@";
String regex = "@";

すると@以外の文字列は受け付けなくなります。このように特定の文字列との一致は正規表現にその文字列を指定することによって実現できます。

では、英字1文字であればどのような文字でも良い場合はどのように正規表現したら良いでしょうか?

英字1文字との一致

[ ...](角括弧)を使います。

角括弧に含まれるいずれか1文字にマッチします。たとえば、 [abc] とするとa,bまたはcとの一致という意味になります。すべてのアルファベット小文字の場合は、列挙するのが大変なので [a-z] とすれば良いです。

以下は英字1文字の正規表現です。

参照:Validator2.java

String rule = "ルール:英字1文字";
String regex = "[a-zA-Z]";

ここまでは全て1文字でした。正規表現の文字数を増やすにはどうしたらいいでしょうか?

文字数の指定

文字数を指定するには{n}(波括弧)を使います。

例えば、{2}とすれば、直前の文字の桁数を指定することになります。

参照:Validator3.java

String rule = "ルール:英字2文字";
String regex = "[a-zA-Z]{2}";

しかし、何文字以上という正規表現はこのままではできません。

どうすればよいでしょうか?

文字数の範囲の指定

何文字以上何文字以下という文字数を制限する正規表現 は{n,m}という正規表現を使います。

直前の文字の最小桁数(n)と最大桁数(m)を指定します。mを空欄にすることで最低の桁数のみを指定することもできます。例えば、 {2,} は最低2桁を意味する正規表現です。

参照:Validator4.java

String rule = "ルール:英字2文字以上";
String regex = "[a-zA-Z]{2,}";

以上でだいたい最初のメールアドレスの正規表現の最後の部分「[a-zA-Z]{2,}$」(トップレベルドメインの部分)は理解できるようになったのではないでしょうか?

あとは最後の$マークですね。これは、行末文字の指定です。

行頭、行末文字の指定

行頭文字を指定するには^(ハット)を行末文字を指定するには$(ドル)を正規表現に使います。^の直後の文字が文字列の先頭になります。$の直前の文字が文字列の末尾になります。

SQL文のLIKE句で例えれば、

「LIKE '090%'」というワイルドカードを使った表現は「'^090'」という正規表現と同じ意味です。

「LIKE '%車'」というワイルドカードを使った表現は「'車$'」という正規表現と同じ意味です。

あわせて確認しておいてください。

例えば、aで始まりzで終わり、真ん中は数字という3桁の文字列は正規表現で

^a[0-9]z$

となります。

参照:Validator5.java

String rule = "ルール:aで始まりzで終わり、真ん中は数字という3桁の文字列";
String regex = "^a[0-9]z$";

となります。

例題1

上記Validator4クラスをコピーして別の名前にして、郵便番号の正規表現を判定するプログラムを作りなさい。

なお、郵便番号は「NNN-NNNN」の形式とする。

「.」(ドット)一文字

次に ドットを正規表現にすることを考えます。実は、ドットは正規表現では任意の1文字を意味します。つまり、「.」は1という数字やaというアルファベット、@という記号など任意の半角文字一文字であれば何でも良いという意味になります。

したがって「.」1文字を指定するのに「.」を使うことはできません。

以下の正規表現を試してみましょう。

参照:Validator6.java

String rule = "ルール:.(のつもり)";
String regex = ".";

1文字であれば何でも受け付けてしまいまいしたね。したがって、文字としてのドットを表現するにはエスケープシーケンスを使う必要があります。エスケープシーケンスのJavaSEでの解説を再掲します。

特別な記号や出力方法を制御するためには「¥」記号を使います。この ¥nなどの文字を、「文字本来の意味から逃がした文字列」という意味で、エスケープシーケンス(escape sequence)と呼びます。この¥記号は日本語版のフォントを使用しているWindows環境の場合です。それ以外の環境ではバックスラッシュ記号(日本語キーボードだと「ろ」が刻印されている)を使います。

https://saycon.co.jp/portal-for-newcomer/javase8/java-7

したがって正しいドット1文字を表す正規表現は以下の通りになります。

参照:Validator7.java

String rule = "ルール:ドット一文字";
String regex = "\\.";

ということで最初のメールアドレスの正規表現につかわれていた「 \. 」も紹介できました。

※ただし、角括弧[]の中にドットを記述する場合はエスケープ不要です

実験1

MySQLを学習した際に販売終了日が2月の車を検索する際にLike演算子を使って以下のように記述しました。

同様のことは正規表現を使っても実現できます。

例えば、以下のように記述することで同じ結果が返ります。

SELECT * FROM cars WHERE end_of_life_dates REGEXP '^[0-9]{4}-02-[0-9]{2}$';

時間が許す限り様々な正規表現をつかってデータベースを検索しなさい。

実験結果のメモ:

1回以上の繰り返し

次にグループ化の()(丸括弧)とOR条件の|(パイプ)を紹介します。この2つは組み合わせて使われることがよくあります。

例えば、010または020のいずれかという場合には以下の正規表現になります。

(010|020)

また、1回以上の繰り返しは「+」記号を使用します。

以下の例を試してみましょう。

参照:Validator8.java

String rule = "ルール:ルール:car,barのいずれかの文字列が1回以上繰り返す";
String regex = "(car|bar)+";

「+」記号 を使った正規表現の場合は“ 1回以上”の繰り返しでしたので、0回は含まれません。

次に0回以上の繰り返しはどのようにすれば表現できるでしょうか?

例題2

上記Validator8クラスをコピーして別の名前にして、携帯電話番号の正規表現を判定するプログラムを作りなさい。

なお、 携帯電話番号 は「NNN-NNNN-NNNN」の形式とし、最初の3桁は070,080又は090のいずれかとする。

0回以上の繰り返し

0回以上の繰り返しには 「*」記号を使用します。

以下の例を試してみましょう。

参照:Validator9.java

String rule = "ルール:一桁の数値に続いてa,b,cのいずれか1文字が0回以上繰り返す";
String regex = "[0-9](a|b|c)*";

「*」記号の0回以上という意味は無くてもよいということです。「+」記号の1回以上と区別してください。

ここまでくれば以下の最初のメールアドレスの正規表現が読めるようになっているのではないでしょうか?

ただし、以下で円マーク(Eclipseでは\記号)が2つ重なっているのはエスケープシーケンスをエスケープしているためです。

[0-9] の部分は \d と記述することもできます。(digitの意)

ただし、\をエスケープする必要があるためJavaプログラムの中では\\dになります。

例題3

上記Validator9クラスをコピーして別の名前にして、Javaの変数名のルールに従っているかどうかを判定するプログラムを作りなさい。

なお、Javaの変数名(識別子)のルールはJavaSEで学んだ通り以下になります。

また、変数名は小文字で始めるJavaプログラマーの慣習には従うものとします。

ただし、予約語との衝突は考慮しないものとします。

Javaでは変数やメソッド、クラスなどの名前のことを識別子と呼びます。

識別子は数字から始めてはいけないことは覚えておきましょう。

また、使える記号はアンダースコア(_)とドル記号($)のみです。

https://saycon.co.jp/portal-for-newcomer/javase8/java-2

1.2 正規表現のまとめ

紹介した正規表現をまとめます。

正規表現説明使用例マッチする例マッチしない例
a文字通りaab
.任意の1文字.a

0
aa
00
[ ...]角括弧に含まれるいずれか1文字[a-zA-Z]car012
{n}直前の文字の桁数[a-zA-Z]{2}aa
bb
a
car
{n,}直前の文字の最小桁数[a-zA-Z]{2,}aa
aaa
aaaa
a
^直後の文字が行の先頭にある場合^carcard
car01
bar
$直前の文字が行の末尾にある場合car$sportscar
racecar
cart
card
\直後の正規表現記号をエスケープするsaycon\.co \ .jpsaycon.co.jp
(…)文字を1つのグループにまとめるcar(ds|ps)cards
carps
cars
|OR条件上記と同じ 上記と同じ 上記と同じ
+直前の文字が 1回以上 繰り返す場合c+arcar
ccar
cccar
bar
*直前の文字が 0回以上 繰り返す場合c*arar
car
ccar
br
bar

これらは正規表現の一部ですからもっと詳しく学びたい人は以下のサイトで調べてみましょう。

1.3 正規表現の学習に役立つサイト

正規表現を視覚化するサイト

https://regexper.com/

正規表現のチェックサイト

https://weblabo.oscasierra.net/tools/regex/

2. ブラウザ側でバリデーションする

なお、ブラウザ側(HTML側)でバリデーションする事もできます。例えば以下のvalidation_postalcode.htmlでは郵便番号形式のバリデーションができます。

ただし、サーバーサイドでのバリデーションが主で、クライアントサイドのバリデーションは補助と考えましょう。なぜなら、攻撃者はフォームを使わずに不正なパケットを直接サーバーに送りつけたり、攻撃者に都合の良いフォームを捏造して攻撃してくるからです。

        <form action="#" method="post" >郵便番号
            <input type="text"
                   size="8"
                   id="postalCode"
                   pattern="^[0-9]{3}-[0-9]{4}$"
                   title="xxx-xxxxの形式で入力してください。"
                   maxlength="8" required 
                   />
            <button type="submit">送信</button>
        </form>

例題4

携帯番号、メールアドレスのHTMLを使ったバリデーションを実装しなさい。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ バリデーションとは、入力されたデータが、あらかじめ規定された形式などに適合しているかどうかをチェックする仕組である

□ 正規表現とは、複数のパターンがありえる文字列を一つの文字列で表現する方法である

「正規表現を使い不正なデータの入力を防ぐ」 最後までお読みいただきありがとうございます。