Java RegExpsで文字をエスケープするためのガイド

Java RegExpsでのエスケープ文字ガイド

1. 概要

Javaの正規表現APIであるjava.util.regexは、パターンマッチングに広く使用されています。 詳細については、このarticleをフォローしてください。

この記事では、正規表現を使用した文字のエスケープに焦点を当て、Javaでそれを行う方法を示します。

2. 特別な正規表現文字

Java正規表現APIドキュメントによると、正規表現にはメタキャラクターとも呼ばれる特殊文字のセットがあります。

文字を特別な意味で解釈するのではなく、文字をそのまま許可する場合は、エスケープする必要があります。 これらの文字をエスケープすることにより、特定の正規表現と文字列を照合するときに、それらを通常の文字として強制的に処理します。

通常、この方法でエスケープする必要があるメタキャラクターは次のとおりです。

<([\{\^-=$!|]})? +。> *

入力Stringを正規表現で表現されたパターンと照合する簡単なコード例を見てみましょう。

このテストは、パターンfooの場合、特定の入力文字列foofに対してそれを示しています。 (ドット文字で終わるfoo)が一致すると、一致が成功したことを示すtrueの値が返されます。

@Test
public void givenRegexWithDot_whenMatchingStr_thenMatches() {
    String strInput = "foof";
    String strRegex = "foo.";

    assertEquals(true, strInput.matches(strRegex));
}

入力String?にドット(。)文字が存在しないのに、なぜ一致が成功するのか不思議に思うかもしれません。

答えは簡単です。 ドット(。)はメタキャラクターです。ここでのドットの特別な意味は、その場所に「任意のキャラクター」が存在できることです。 したがって、一致するものが見つかったとマッチャーがどのように判断したかは明らかです。

ドット(。)文字をその固有の意味で扱いたくないとしましょう。 代わりに、ドット記号として解釈されるようにします。 これは、前の例では、パターンfoo.が入力String.で一致するようにしたくないことを意味します。

このような状況をどのように処理しますか? 答えは:we need to escape the dot (.) character so that its special meaning gets ignored.

次のセクションで詳しく見ていきましょう。

3. エスケープ文字

正規表現のJava APIドキュメントによると、特別な意味を持つ文字をエスケープする方法は2つあります。 つまり、それらを強制的に通常の文字として扱うようにします。

それらが何であるかを見てみましょう:

  1. メタキャラクターの前にバックスラッシュ(\)を付けます

  2. メタ文字を\Q\Eで囲みます

これは、先ほど見た例で、ドット文字をエスケープしたい場合、ドット文字の前にバックスラッシュ文字を置く必要があることを意味します。 または、ドット文字を\ Qと\ Eの間に配置することもできます。

3.1. バックスラッシュを使用したエスケープ

これは、正規表現でメタキャラクターをエスケープするために使用できるテクニックの1つです。 ただし、バックスラッシュ文字はJavaStringリテラルでもエスケープ文字であることがわかっています。 したがって、バックスラッシュ文字を使用して文字(\文字自体を含む)の前に置く場合は、バックスラッシュ文字を2倍にする必要があります。

したがって、この例では、このテストに示すように正規表現を変更する必要があります。

@Test
public void givenRegexWithDotEsc_whenMatchingStr_thenNotMatching() {
    String strInput = "foof";
    String strRegex = "foo\\.";

    assertEquals(false, strInput.matches(strRegex));
}

ここでは、ドット文字がエスケープされているため、マッチャーは単にそれをドットとして扱い、ドットで終わるパターンを見つけようとします(つまり、 foo.)。

この場合、そのパターンの入力Stringに一致するものがないため、falseが返されます。

3.2. \ Q&\ Eを使用したエスケープ

または、\Q\Eを使用して特殊文字をエスケープすることもできます。 \Qは、\Eまでのすべての文字をエスケープする必要があることを示し、\Eは、\Qで開始されたエスケープを終了する必要があることを意味します。

これは、\Q\Eの間にあるものはすべてエスケープされることを意味します。

ここに示すテストでは、Stringクラスのsplit()は、提供された正規表現を使用して照合を行います。

私たちの要件は、パイプ(|)文字で入力文字列を単語に分割することです。 そのため、正規表現パターンを使用してそうします。

パイプ文字は、正規表現でエスケープする必要があるメタ文字です。

ここで、エスケープは、パイプ文字を\Q\Eの間に配置することによって行われます。

@Test
public void givenRegexWithPipeEscaped_whenSplitStr_thenSplits() {
    String strInput = "foo|bar|hello|world";
    String strRegex = "\\Q|\\E";

    assertEquals(4, strInput.split(strRegex).length);
}

4. Pattern.Quote(String S)メソッド

java.util.regex.PatternクラスのPattern.Quote(String S)メソッドは、指定された正規表現パターンStringをリテラルパターンString.に変換します。これは、入力Stringのすべてのメタ文字が通常の文字として扱われます。

この方法を使用すると、指定されたStringをラップするため、\Q\Eを使用するよりも便利な方法になります。

このメソッドの動作を見てみましょう。

@Test
public void givenRegexWithPipeEscQuoteMeth_whenSplitStr_thenSplits() {
    String strInput = "foo|bar|hello|world";
    String strRegex = "|";

    assertEquals(4,strInput.split(Pattern.quote(strRegex)).length);
}

このクイックテストでは、Pattern.quote()メソッドを使用して、指定された正規表現パターンをエスケープし、それをStringリテラルに変換します。 つまり、正規表現パターンに存在するすべてのメタキャラクターをエスケープします。 \Qおよび\Eと同様のジョブを実行しています。

パイプ文字はPattern.quote()メソッドによってエスケープされ、split()はそれをStringリテラルとして解釈し、それによって入力を分割します。

ご覧のとおり、これははるかにクリーンなアプローチであり、開発者はすべてのエスケープシーケンスを覚える必要はありません。

5. 追加の例

java.util.regex.MatcherreplaceAll()メソッドがどのように機能するかを見てみましょう。

特定の文字Stringのすべての出現箇所を別の文字に置き換える必要がある場合は、正規表現を渡すことでこのメソッドを使用できます。

$文字が複数回出現する入力があるとします。 取得したい結果は、$文字が£に置き換えられた同じ文字列です。

このテストは、パターン$がエスケープされずに渡される方法を示しています。

@Test
public void givenRegexWithDollar_whenReplacing_thenNotReplace() {

    String strInput = "I gave $50 to my brother."
      + "He bought candy for $35. Now he has $15 left.";
    String strRegex = "$";
    String strReplacement = "£";
    String output = "I gave £50 to my brother."
      + "He bought candy for £35. Now he has £15 left.";

    Pattern p = Pattern.compile(strRegex);
    Matcher m = p.matcher(strInput);

    assertThat(output, not(equalTo(m.replaceAll(strReplacement))));
}

テストは、$£に正しく置き換えられていないことを表明します。

正規表現パターンをエスケープすると、置換が正しく行われ、次のコードスニペットに示すようにテストに合格します。

@Test
public void givenRegexWithDollarEsc_whenReplacing_thenReplace() {

    String strInput = "I gave $50 to my brother."
      + "He bought candy for $35. Now he has $15 left.";
    String strRegex = "\\$";
    String strReplacement = "£";
    String output = "I gave £50 to my brother."
      + "He bought candy for £35. Now he has £15 left.";
    Pattern p = Pattern.compile(strRegex);
    Matcher m = p.matcher(strInput);

    assertEquals(output,m.replaceAll(strReplacement));
}

ここで\$に注意してください。これは、$の文字をエスケープし、パターンを正常に一致させることによってトリックを実行します。

6. 結論

この記事では、Javaの正規表現での文字のエスケープについて説明しました。

正規表現をエスケープする必要がある理由と、それを実現するさまざまな方法について説明しました。

いつものように、この記事に関連するソースコードはover on GitHubにあります。