Синтаксис ссылок на ссылки в строках замены (почему знак доллара?)
В Java и, кажется, на нескольких других языках, обратным следам в шаблоне предшествует обратная косая черта (например, \1
, \2
, \3
и т.д.), но в заменяющей строке им предшествует знак доллара (например, $1
, $2
, $3
, а также $0
).
Вот фрагмент, иллюстрирующий:
System.out.println(
"left-right".replaceAll("(.*)-(.*)", "\\2-\\1") // WRONG!!!
); // prints "2-1"
System.out.println(
"left-right".replaceAll("(.*)-(.*)", "$2-$1") // CORRECT!
); // prints "right-left"
System.out.println(
"You want million dollar?!?".replaceAll("(\\w*) dollar", "US\\$ $1")
); // prints "You want US$ million?!?"
System.out.println(
"You want million dollar?!?".replaceAll("(\\w*) dollar", "US$ \\1")
); // throws IllegalArgumentException: Illegal group reference
Вопросы:
- Используется ли
$
для обратных ссылок в строках замещения, уникальных для Java? Если нет, то какой язык начал? Какие ароматы используют его, а что нет?
- Почему это хорошая идея? Почему бы не придерживаться одного и того же синтаксиса шаблонов? Разве это не привело бы к более сплоченному и более легкому изучению языка?
- Разве синтаксис не был бы более упорядоченным, если бы утверждения 1 и 4 в приведенных выше были "правильными" вместо 2 и 3?
Ответы
Ответ 1
Является ли использование $для обратных ссылок в строках замещения, уникальных для Java?
Нет. Perl использует его, и Perl, безусловно, предшествует классу Java Pattern
. Поддержка регулярного выражения Java явно описывается в терминах регулярных выражений Perl.
Например: http://perldoc.perl.org/perlrequick.html#Search-and-replace
Почему это хорошая идея?
Ну, очевидно, вы не думаете, что это хорошая идея! Но одна из причин, по которой это хорошая идея, - сделать поддержку поиска и замены Java совместимой с Perl.
Существует еще одна возможная причина, по которой $
можно было бы рассматривать как лучший выбор, чем \
. Это значит, что \
должен быть записан как \\
в литерале Java String.
Но все это чистое предположение. Никто из нас не был в комнате, когда были приняты проектные решения. И в конечном итоге на самом деле не имеет значения, почему они спроектировали синтаксис замены String таким образом. Решения принимались и устанавливались конкретными, и любое дальнейшее обсуждение носит чисто академический характер... если только вы просто не собираетесь разрабатывать новый язык или новую библиотеку регулярных выражений для Java.
Ответ 2
После некоторых исследований я понял проблемы сейчас: Perl имел, чтобы использовать другой символ для обратных ссылок и замены обратных ссылок, а в то время как java.util.regex.*
не имеет, чтобы следовать этому примеру, он выбирает, а не техническую, но довольно традиционную причину.
На стороне Perl
(Пожалуйста, имейте в виду, что все, что я знаю о Perl в этот момент, происходит от чтения статей в Википедии, поэтому не стесняйтесь исправить любые ошибки, которые я, возможно, сделал)
Причиной, по которой это было, сделано в Perl, является следующее:
- Perl использует
$
как сигил (т.е. символ, прикрепленный к имени переменной).
- Строковые литералы Perl имеют переменную интерполяцию.
- Perl regex фактически захватывает группы как переменные
$1
, $2
и т.д.
Таким образом, из-за того, как интерпретируется Perl и как работает его механизм регулярных выражений, необходимо использовать предыдущую косую черту для обратных ссылок (например, \1
) в шаблоне, потому что если вместо этого используется сигма $
(например, $1
), это вызовет непреднамеренную переменную интерполяцию в шаблон.
Строка замены из-за того, как она работает в Perl, оценивается в контексте каждого соответствия. Для Perl наиболее естественно использовать переменную интерполяцию, поэтому механизм regex захватывает группы в переменные $1
, $2
и т.д., Чтобы сделать эту работу без проблем с остальной частью языка.
Ссылки
На стороне Java
Java - это совсем другой язык, чем Perl, но, самое главное, здесь нет переменной интерполяции. Кроме того, replaceAll
- вызов метода, и, как и во всех вызовах метода в Java, аргументы оцениваются один раз, до вызова метода.
Таким образом, функция переменной интерполяции сама по себе недостаточно, так как по существу заменяющая строка должна быть переоценена для каждого совпадения, а это просто не семантика вызовов методов в Java. Строка замены с переменным интерполированием, которая оценивается до replaceAll
, даже вызвана, практически бесполезна; интерполяция должна выполняться во время метода в каждом матче.
Так как это не семантика языка Java, replaceAll
должна выполнить эту интерполяцию "точно в момент" вручную. Таким образом, существует абсолютно никакой технической причины, почему $
является символом escape для обратных ссылок в замещающих строках. Это могло бы быть очень хорошо \
. И наоборот, обратные ссылки в шаблоне также могли быть экранированы с помощью $
вместо \
, и он все равно работал бы точно так же технически.
Причина, по которой Java делает регулярное выражение так, как она делает, является чисто традиционной: она просто следует за прецедентом, установленным Perl.