最近、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このようにめちゃくちゃになったのですか?
@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正規表現言語は、これを特定の(正規表現)エスケープシーケンスとして認識しません。ただし、正規表現言語では、バックスラッシュとそれに続くアルファベット以外の文字は、後者の文字を意味します。バックスラッシュとそれに続く改行文字...は、改行と同じことを意味します。
Java用に1回、正規表現用に1回、2回エスケープする必要があります。
Javaコードは
"\\\\"
の正規表現文字列を作成します
"\\" - two chars
しかし、正規表現にもエスケープが必要なので、
\ - one symbol
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