Подтвердить что ты не робот

Почему String.replaceAll() в java требует 4 слэша "\\\\" в regex, чтобы фактически заменить "\"?

Недавно я заметил, что String.replaceAll(regex, replacement) ведет себя очень странно, когда дело доходит до escape-символа "\" (слэш)

Например, рассмотрим строку с пути к файлу - String text = "E:\\dummypath" и мы хотим заменить "\\" на "/".

text.replace("\\","/") дает результат "E:/dummypath", тогда как text.replaceAll("\\","/") вызывает исключение java.util.regex.PatternSyntaxException.

Если мы хотим реализовать ту же функциональность с replaceAll(), нам нужно записать ее как, text.replaceAll("\\\\","/")

Одна заметная разница replaceAll() имеет свои аргументы как reg-ex, тогда как replace() имеет аргументы символьной последовательности!

Но text.replaceAll("\n","/") работает точно так же, как и его эквивалент char -последовательности text.replace("\n","/")

Копаем глубже: Еще более странное поведение можно наблюдать, когда мы пытаемся использовать другие входы.

Позволяет назначить text="Hello\nWorld\n"

Теперь, text.replaceAll("\n","/"), text.replaceAll("\\n","/"), text.replaceAll("\\\n","/") все эти три дают одинаковый вывод Hello/World/

Java действительно перепутала с reg-ex наилучшим образом, я чувствую! Никакой другой язык, похоже, не имеет такого игривого поведения в регистре. Любая конкретная причина, почему Java испортилась так?

4b9b3361

Ответ 1

Ответ @Peter Lawrey описывает механику. "Проблема" заключается в том, что обратная косая черта является escape-символом как в строковых литералах Java, так и в мини-языке регулярных выражений. Поэтому, когда вы используете строковый литерал для представления регулярного выражения, необходимо рассмотреть два набора экранов... в зависимости от того, что вы хотите, чтобы регулярное выражение означало.

Но почему это так?

Это историческая вещь. Первоначально Java не имела регулярных выражений. Синтаксические правила для литералов Java String были взяты из C/C++, которые также не имели встроенной поддержки регулярных выражений. Неловкость двойного экранирования не проявилась на Java, пока они не добавили поддержку регулярных выражений в виде класса Pattern... в Java 1.4.

Так как другим языкам удается избежать этого?

Они делают это, предоставляя прямую или косвенную синтаксическую поддержку регулярных выражений на самом языке программирования. Например, в Perl, Ruby, Javascript и многих других языках существует синтаксис для шаблонов/регулярных выражений (например, '/pattern/'), где правила экранирования строк литералов не применяются. В С# и Python они предоставляют альтернативный "сырой" строковый литерал, в котором обратные косые черты не исчезают. (Но учтите, что если вы используете обычный синтаксис строки С#/Python, у вас есть проблема Java с двойным экранированием.)


Почему text.replaceAll("\n","/"), text.replaceAll("\\n","/") и text.replaceAll("\\\n","/") дают такой же выход?

Первый случай - символ новой строки на уровне String. Язык регулярных выражений Java обрабатывает все неспецифические символы как соответствующие самим.

Второй случай - обратная косая черта, сопровождаемая "n" на уровне String. Язык регулярных выражений Java интерпретирует обратную косую черту, за которой следует "n" в качестве новой строки.

Последний случай - обратная косая черта, сопровождаемая символом новой строки на уровне String. Язык регулярных выражений Java не распознает это как конкретную (регулярную) escape-последовательность. Однако в языке регулярных выражений обратная косая черта, сопровождаемая любым неалфавитным символом, означает последний символ. Таким образом, обратная косая черта, сопровождаемая символом новой строки... означает то же самое, что и новая строка.

Ответ 2

Вам нужно выполнить esacpe дважды, один раз для Java, один раз для регулярного выражения.

Код Java

"\\\\"

создает строку регулярных выражений

"\\" - two chars

но регулярное выражение нуждается в побеге, поэтому оно превращается в

\ - one symbol

Ответ 3

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 нет литерала регулярного выражения: вы пишете регулярное выражение в строковом литерале (в отличие, например, от JavaScript, где вы можете написать /\d+/). Но в строковом литерале \ также имеет особое значение, например, в \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

Ответ 4

Это связано с тем, что Java пытается дать \ специальное значение в заменяющей строке, так что\$будет буквенным знаком $, но в процессе они, похоже, удалили фактическое специальное значение \

Хотя text.replaceAll("\\\\","/"), по крайней мере, можно считать приемлемым в некотором смысле (хотя он сам не совсем прав), все три исполнения text.replaceAll("\n","/"), text.replaceAll("\\n","/"), text.replaceAll("\\\n","/"), дающие тот же результат, кажутся даже больше веселья. Просто противоречит тому, почему они ограничили функционирование text.replaceAll("\\","/") по той же причине.

Java не испортил регулярные выражения. Это связано с тем, что Java любит испортить кодировщики, пытаясь сделать что-то уникальное и другое, когда это совсем не требуется.

Ответ 5

Один из способов решения этой проблемы - заменить обратную косую черту другим символом, использовать этот символ ожидания для промежуточных замен, а затем преобразовать его обратно в обратную косую черту в конце. Например, чтобы преобразовать "\ r\n" в "\n":

String out = in.replace('\\','@').replaceAll("@[email protected]","@n").replace('@','\\');

Конечно, это не сработает, если вы выберете заменяющий символ, который может появиться во входной строке.

Ответ 6

Я думаю, что java действительно запуталась с регулярным выражением в String.replaceAll();

Кроме java, я никогда не видел регулярного выражения синтаксического анализа таким образом. Вы будете смущены, если вы использовали регулярное выражение на некоторых других языках.

В случае использования "\\" в строке замены вы можете использовать java.util.regex.Matcher.quoteReplacement(String)

String.replaceAll("/", Matcher.quoteReplacement("\\"));

Используя этот класс Matcher, вы можете получить ожидаемый результат.