String.replaceAll (регулярное выражение) дважды выполняет одну и ту же замену

#java #regex #string #replaceall

#java #регулярное выражение

Вопрос:

Кто-нибудь может сказать мне, почему

 System.out.println("test".replaceAll(".*", "a"));
  

Приводит к

 aa
  

Обратите внимание, что следующее имеет тот же результат:

 System.out.println("test".replaceAll(".*$", "a"));
  

Я тестировал это на Java 6 и 7, и оба, похоже, ведут себя одинаково.
Я что-то упускаю или это ошибка в движке регулярных выражений java?

Ответ №1:

Это не аномалия: .* может соответствовать чему угодно.

Вы просите заменить все вхождения:

  • первое вхождение действительно соответствует всей строке, поэтому механизм регулярных выражений запускается с конца ввода для следующего соответствия;
  • но .* также соответствует пустой строке! Поэтому он сопоставляет пустую строку в конце входных данных и заменяет ее на a .

Использование . вместо этого не приведет к возникновению этой проблемы, поскольку это регулярное выражение не может соответствовать пустой строке (для соответствия требуется хотя бы один символ).

Или используйте .replaceFirst() только для замены первого вхождения:

 "test".replaceFirst(".*", "a")
       ^^^^^^^^^^^^
  

Теперь интересно рассмотреть, почему .* он ведет себя так, как есть, и не совпадает более двух раз (теоретически может). Смотрите ниже:

 # Before first run
regex: |.*
input: |whatever
# After first run
regex: .*|
input: whatever|
#before second run
regex: |.*
input: whatever|
#after second run: since .* can match an empty string, it it satisfied...
regex: .*|
input: whatever|
# However, this means the regex engine matched an empty input.
# All regex engines, in this situation, will shift
# one character further in the input.
# So, before third run, the situation is:
regex: |.*
input: whatever<|ExhaustionOfInput>
# Nothing can ever match here: out
  

Обратите внимание, что, как отмечает @A.H. в комментариях, не все движки регулярных выражений ведут себя таким образом. GNU sed , например, будет считать, что он исчерпал входные данные после первого совпадения.

Комментарии:

1. Согласен. Это верно и для Perl. perl -le '$x = "test"; $x =~ s/.*/a/g; print $x' выдает «aa».

2. @ChrisDolan: sed выдает только a , но я сомневаюсь, что это ошибка. 🙂

3. @A.H. действительно, да… Мне нужно снова прочитать «Освоение регулярных выражений»

4. Спасибо за отзыв, я долгое время использую регулярные выражения, но никогда не сталкивался с этим. Каждый день изучайте что-то новое…

5. Другой способ решить эту проблему: используйте ^.* — это будет соответствовать только a один раз по очевидным причинам.

Ответ №2:

Принятый ответ еще не показал этого, поэтому вот альтернативный способ исправить ваше регулярное выражение:

 System.out.println("test".replaceAll("^.*$", "a"));
  

Обратите внимание, я использую оба термина: ^ и $ . $ Не является строго необходимым для данного конкретного случая, но я нахожу добавление обоих наименее загадочным.