web-dev-qa-db-ja.com

JavaのString.replaceAll()が実際に "\"を置き換えるために正規表現で4つのスラッシュ "\\\\"を必要とするのはなぜですか?

最近、String.replaceAll(regex、replacement)がエスケープ文字「\」(スラッシュ)に関して非常に奇妙な動作をすることに気付きました。

たとえば、filepath-_String text = "E:\\dummypath"_の文字列があり、_"\\"_を_"/"_に置き換えたいとします。

text.replace("\\","/")は出力_"E:/dummypath"_を返しますが、text.replaceAll("\\","/")は例外_Java.util.regex.PatternSyntaxException_を発生させます。

replaceAll()で同じ機能を実装する場合は、text.replaceAll("\\\\","/")のように記述する必要があります。

注目すべき違いの1つは、replaceAll()の引数がreg-exであるのに対し、replace()の引数は文字列です!

ただし、text.replaceAll("\n","/")は、同等の文字シーケンスtext.replace("\n","/")とまったく同じように機能します。

Digging Deeper:他の入力を試みると、さらに奇妙な動作が観察されます。

_text="Hello\nWorld\n"_を割り当てましょう

これで、text.replaceAll("\n","/")text.replaceAll("\\n","/")text.replaceAll("\\\n","/")これら3つすべてが同じ出力_Hello/World/_を提供します

Javaは、私が感じる最高の方法でreg-exを本当に台無しにしました! reg-exでこれらの遊び心のある動作を行う言語は他にありません。特定の理由、なぜJavaこのようにめちゃくちゃになったのですか?

27
Bharath

@Peter Lawreyの答えはその仕組みを説明しています。 「問題」は、バックスラッシュがJava文字列リテラルと正規表現のミニ言語の両方のエスケープ文字であるということです。したがって、文字列リテラルを使用して正規表現を表す場合、考慮すべきエスケープのセット...正規表現の意味に応じて。

しかし、なぜそうなのでしょうか?

それは歴史的なものです。 Javaもともと正規表現はまったくありませんでした。Java文字列リテラルはC/C++から借用されていました。正規表現のサポート。二重エスケープの厄介さは、JavaでPatternクラスの形式で正規表現サポートを追加するまで明らかになりませんでした...でJava 1.4。

それでは、他の言語はどうやってこれを避けることができますか?

彼らは正規表現の直接または間接の構文サポートを提供することでそれを行いますプログラミング言語自体で。たとえば、Perl、Ruby、Javascript、および他の多くの言語には、文字列リテラルのエスケープ規則が適用されないパターン/正規表現(例: '/ pattern /')の構文があります。 C#およびPythonでは、バックスラッシュがエスケープではない代替の「生の」文字列リテラル構文を提供します。 (ただし、通常のC#/ Python文字列構文を使用すると、Java二重エスケープの問題が発生します。)


なぜtext.replaceAll("\n","/")text.replaceAll("\\n","/")、およびtext.replaceAll("\\\n","/")がすべて同じ出力を与えるのですか?

最初のケースは、文字列レベルの改行文字です。 Java regex言語は、すべての非特殊文字を自分自身と一致するものとして扱います。

2番目のケースは、バックスラッシュとそれに続く文字列レベルの「n」です。 Java正規表現言語は、バックスラッシュとそれに続く「n」を改行として解釈します。

最後のケースは、バックスラッシュとそれに続く文字列レベルの改行文字です。 Java正規表現言語は、これを特定の(正規表現)エスケープシーケンスとして認識しません。ただし、正規表現言語では、バックスラッシュとそれに続くアルファベット以外の文字は、後者の文字を意味します。バックスラッシュとそれに続く改行文字...は、改行と同じことを意味します。

23
Stephen C

Java用に1回、正規表現用に1回、2回エスケープする必要があります。

Javaコードは

"\\\\"

の正規表現文字列を作成します

"\\" - two chars

しかし、正規表現にもエスケープが必要なので、

\ - one symbol
26
Peter Lawrey

1)単一の\をJavaのreplaceAllメソッドを使用して置き換えたいとしましょう:

   \
   ˪--- 1) the final backslash

2)JavaのreplaceAllメソッドは、最初の引数として正規表現を取ります。 正規表現リテラルでは、\には特別な意味があります。 \dのショートカットである[0-9](任意の数字)。 正規表現リテラルでメタ文字をエスケープする方法は、その前に\を付けることです。

 \ \
 | ˪--- 1) the final backslash
 |
 ˪----- 2) the backslash needed to escape 1) in a regex literal

3)Javaには正規表現リテラルはありません:文字列リテラルに正規表現を記述します(たとえば、/\d+/を記述できるJavaScriptとは異なります)。しかし文字列リテラルでは、\も特別な意味を持ちます。 \n(改行)または\t(タブ) 文字列リテラルでメタ文字をエスケープする方法は、その前に\を付けることです。

\\\\
|||˪--- 1) the final backslash
||˪---- 3) the backslash needed to escape 1) in a string literal
|˪----- 2) the backslash needed to escape 1) in a regex literal
˪------ 3) the backslash needed to escape 2) in a string literal
5
sp00m