正規表現 (regular expression)とは、文字列の集合(パターン)を表す汎用的な記法です。 例えば、文字列「book または cook」のことを正規表現で [bc]ook と書くことができます。 [bc] は 「b か c かどちらか 1 文字」という意味です。 文字列「h1~h6 のいずれか」を表すには h[1-6] と書きます。
正規表現などで表される文字列パターンに合致する文字列を探すことを、 パターンマッチと言います。 Java、Perl、Ruby、Python などの言語は正規表現を扱うことができるため、 パターンマッチを行うプログラムを容易に作成することができます。
正規表現は Java が生まれる前からあるものです。 興味のある人は、UNIX のコマンド grep、sed、awk を調べてみましょう。
記法については、Java における正規表現のクラス java.util.regex.Pattern の説明文を参照してください。 ここではよく使う記法について例を挙げておきます。
正規表現の例 | 意味 | マッチする例 |
---|---|---|
通常の文字 | そのままの文字 | 「abc」なら「abc」 |
. | 任意の1文字 | 「a」でも「b」でも何でも |
\n | 改行 | |
\t | タブ | |
\. \( \) \[ \] \^ \$ \\ | 記号ではなく文字 | 「.」「(」「)」「[」「]」「^」「$」「\」 |
「\」はエスケープ文字です。本当はバックスラッシュ「\」ですが、日本語環境では円マーク「¥」が表示されます。
Java のソースコード内の文字列定数(""内)では、 ただ「\」と書くと文字列定数としての特殊記号として扱われてしまうため、 そもそも文字「\」を「\\」と書く必要があります。 よって、正規表現としての「\.」は「\\.」、「\\」は「\\\\」などと書く必要があります。
正規表現の例 | 意味 | マッチする例 |
---|---|---|
[abc] | abcのうち1文字 | 「a」「b」「c」のいずれか |
[^abc] | abc以外の1文字 | 「d」など |
[A-Za-z] | AからZ、aからzのうちの1文字 | 「d」など |
\d | 数字1文字 | 「0」から「9」までのいずれか |
\w | 英数字1文字 | 「a」「B」「7」など |
\s | 空白文字1文字 | 空白、改行、タブなど |
\p{InBasicLatin} | ラテン文字1文字 | 「a」「B」記号など |
\p{InHiragana} | 平仮名1文字 | 「あ」「ん」など |
\p{InKatakana} | 片仮名1文字 | 「ア」「ン」など |
\p{InCJKUnifiedIdeographs} | 漢字1文字 | 「漢」「字」など |
上記の \p{...} は Unicodeブロック と呼ばれるものです。
正規表現の例 | 意味 | マッチする例 |
---|---|---|
^ | 行頭 | 「^un」は「un」で始まる文字列 |
$ | 行末 | 「ing$」は「ing」で終わる文字列 |
正規表現の例 | 意味 | マッチする例 |
---|---|---|
? | 直前の文字が0個か1個 | 「e?grep」は「grep」か「egrep」 |
* | 直前の文字が0個以上 | 「.*」は 0文字以上の任意の文字列 |
+ | 直前の文字が1個以上 | 「.+」は 1文字以上の任意の文字列 |
\{n} | 直前の文字がn個 | 「a\{2}」は「aa」 |
\{n,} | 直前の文字がn個以上 | 「a\{2,}」は「aa」「aaa」... |
\{n,m} | 直前の文字がn個以上,m個以下 | 「a\{2,3}」は「aa」「aaa」 |
+? | 直前の文字が1個以上,ただしマッチする最小の範囲 |
正規表現の例 | 意味 | 備考 |
---|---|---|
tdu|dendai | tdu または dendai | 範囲を区切るため全体を()で囲むことが多い |
() | グループ分け | group(n)でn番目のグループにマッチした文字列を参照できる |
Javaには正規表現に関する機能を実現するためのパッケージ java.util.regex があります (Java SDK 1.4 で導入)。 このパッケージにより、強力な正規表現の機能を持つ Perl とほぼ同等の機能が実現されています。
キーボードからまず正規表現のパターンを入力し、 つぎに任意の文字列を入力してマッチするか確認することができるプログラムを示します。
import java.util.*; import java.util.regex.*; class PatternTester { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("テストする正規表現を入力してください。"); Pattern pattern = Pattern.compile(scanner.nextLine()); System.out.println("マッチさせる文字列を入力してください(Ctrl-dで終了)。"); while (scanner.hasNext()) { // Ctrl-d が来ない限り繰り返す // 1行分の文字列を読み込む String nextLine = scanner.nextLine(); // nextLine に対してパターンマッチをする Matcher インスタンスを生成 Matcher matcher = pattern.matcher(nextLine); // 先頭から探せる限り探す while (matcher.find()) { System.out.println("マッチしました: " + matcher.group()); } } } }
なお、Scanner クラスはそれ自体が正規表現に対応しているため、 Matcher を明示的に使わない書き方もできます。
System.out.println("マッチさせるテキストを入力してください(Ctrl-dで入力終了)。"); while(true) { String string = scanner.findInLine(pattern); if(string == null) break; else System.out.println("マッチしました: " + string); }
Webページ(HTML文書)などで使われるタグを除去します。 正規表現でタグをマッチさせ、replaceAll メソッドでマッチした部分を空文字列に置き換えます。
1つのタグがマッチする正規表現を考えます。 <.+?> という正規表現で、< > に囲まれた1文字以上の文字列を表します。 なお、.+? は最短のマッチを表します。 ちょっと考えると .+ (任意の文字の1文字以上の連続)でよさそうに思えますが、 そうすると以下のような場合、
<em>content</em>
.+ がマッチする範囲は「 em>content</em 」となってしまいます。
import java.io.*; import java.net.*; import java.util.regex.*; public class TagRemover { public static void main(String[] args) { try { // ネットワーク接続の準備 URL url = new URL("http://web.dendai.ac.jp/"); URLConnection connection = url.openConnection(); connection.connect(); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); // 1行ごとにタグ除去 Pattern pattern = Pattern.compile("<.+?>"); String line; while ((line = reader.readLine()) != null) { Matcher matcher = pattern.matcher(line); String plainText = matcher.replaceAll(""); if(plainText.length() != 0) { // 空文字列はスキップ System.out.println(plainText); } } reader.close(); } catch (IOException e) { e.printStackTrace(); } } }
正規表現では () でグループを表し、 その部分にマッチした文字列を参照することができます。 グループには先頭から 1,2,... と番号がつけられていますので、 group(番号n) で n番目のグループにマッチした文字列が得られます。
import java.util.regex.*; class LinkFinder { public static void main(String args[]) { Pattern pattern = Pattern.compile("href=\"(.+?)\""); // グループの指定が1つ(1番目) Matcher matcher = pattern.matcher("<a href=\"index.html\">トップへ</a>"); if(matcher.find()) { System.out.println(matcher.group(1)); // 1番目のグループにマッチした文字列を取り出す } } }
文字列の置換と分割については、 String クラスから直接行えるメソッドが用意されています。