1. 概要

文字列を文字列と一致させるのが難しい場合があります 正規表現。 たとえば、正確に一致させたいものがわからない場合でも、直前にあるものや直後にないものなど、その周囲を認識できます。 このような場合、ルックアラウンドアサーションを使用できます。 これらの式は、何かが一致するかどうかを示すだけで、結果には含まれないため、アサーションと呼ばれます。

このチュートリアルでは、4種類の正規表現ルックアラウンドアサーションを使用する方法を見ていきます。

2. ポジティブルックアヘッド

javaファイルのインポートを分析したいとします。 まず、staticキーワードがimportキーワードの後に続くことを確認して、staticであるインポートステートメントを探しましょう。

式の(?= criteria)構文でポジティブ先読みアサーションを使用して、メイン式importの後の文字グループstaticと一致させましょう。

Pattern pattern = Pattern.compile("import (?=static).+");

Matcher matcher = pattern
  .matcher("import static org.junit.jupiter.api.Assertions.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.jupiter.api.Assertions.assertEquals;", matcher.group());

assertFalse(pattern.matcher("import java.util.regex.Matcher;").find());

3. ネガティブルックアヘッド

次に、前の例とは正反対のことを行い、staticではないインポートステートメントを探します。 staticキーワードがimportキーワードの後に続かないことを確認してこれを行いましょう。

式の(?! criteria)構文で負の先読みアサーションを使用して、文字のグループstaticがメインの式importの後に一致しないようにします。 :

Pattern pattern = Pattern.compile("import (?!static).+");

Matcher matcher = pattern.matcher("import java.util.regex.Matcher;");
assertTrue(matcher.find());
assertEquals("import java.util.regex.Matcher;", matcher.group());

assertFalse(pattern
  .matcher("import static org.junit.jupiter.api.Assertions.assertEquals;").find());

4. Javaでのルックビハインドの制限

Java 8までは、+*などのバインドされていない量指定子が後読みアサーション内で許可されないという制限に遭遇する可能性がありました。 つまり、たとえば、次のアサーションはJava8までPatternSyntaxExceptionをスローします。

  • (?foと1つ以上のo文字が前にある場合、barと一致させたくない場合
  • (?barの前にf文字が続き、その後に0個以上の o 文字が続く場合、barと一致させたくない場合
  • (? <!fo{2,})bar 、 どこ 2つ以上のo文字を含むfooがその前にある場合、バーを一致させたくありません

回避策として、たとえば、上限が指定された中括弧の数量詞を使用する場合があります。 (? <!fo{2,4})bar 、ここで、 o 次の文字 f 文字を4に。

Java 9以降、後読みでバインドされていない量指定子を使用できます。 ただし、正規表現の実装ではメモリを消費するため、たとえば、適切な上限のある後読みでのみ数量詞を使用することをお勧めします。 (? <!fo{2,20})bar それ以外の (? <!fo{2,2000})bar

5. ポジティブルックビハインド

分析でJUnit4とJUnit5のインポートを区別したいとします。 まず、assertEqualsメソッドのインポートステートメントがjupiterパッケージからのものであるかどうかを確認しましょう。

ポジティブビハインドアサーションを使用してみましょう (?<=基準) 文字グループに一致する式の構文木星主な表現の前に。* assertEquals

Pattern pattern = Pattern.compile(".*(?<=jupiter).*assertEquals;");

Matcher matcher = pattern
  .matcher("import static org.junit.jupiter.api.Assertions.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.jupiter.api.Assertions.assertEquals;", matcher.group());

assertFalse(pattern.matcher("import static org.junit.Assert.assertEquals;").find());

6. ネガティブルックビハインド

次に、前の例の正反対を実行して、jupiterパッケージからではないインポートステートメントを探しましょう。

これを行うには、ネガティブルックビハインドアサーションを使用してみましょう。 (? <!criteria) 文字のグループを確実にするための式の構文木星。{0,30} 私たちの主な表現の前に一致することはできません assertEquals

Pattern pattern = Pattern.compile(".*(?<!jupiter.{0,30})assertEquals;");

Matcher matcher = pattern.matcher("import static org.junit.Assert.assertEquals;");
assertTrue(matcher.find());
assertEquals("import static org.junit.Assert.assertEquals;", matcher.group());

assertFalse(pattern
  .matcher("import static org.junit.jupiter.api.Assertions.assertEquals;").find());

7. 結論

この記事では、4種類の正規表現ルックアラウンドを使用して、文字列を正規表現と照合するという難しいケースを解決する方法を説明しました。

いつものように、この記事のソースコードはGitHubから入手できます。