1. Обзор
API регулярных выражений в Java, java.util.regex широко используется для сопоставления шаблонов. Чтобы узнать больше, вы можете следить за этой статьей .
В этой статье мы сосредоточимся на экранировании символов в регулярном выражении и покажем, как это можно сделать в Java.
2. Специальные символы регулярных выражений
Согласно документации API регулярных выражений Java, в регулярном выражении присутствует набор специальных символов, также известных как метасимволы.
Когда мы хотим оставить персонажей такими, какие они есть, вместо того, чтобы интерпретировать их с их особым значением, нам нужно избежать их. Экранируя эти символы, мы заставляем их рассматриваться как обычные символы при сопоставлении строки с заданным регулярным выражением.
Метасимволы, которые нам обычно нужно избежать таким образом, являются:
<([{\^-=$!|]})?*+.>
Давайте рассмотрим простой пример кода, в котором мы сопоставляем входную строку с шаблоном, выраженным в регулярном выражении.
Этот тест показывает, что для заданной входной строки food при совпадении шаблона foo . ( foo , заканчивающегося символом точки), он возвращает значение true , которое указывает на то, что совпадение успешно.
@Test public void givenRegexWithDot_whenMatchingStr_thenMatches() { String strInput = "foof"; String strRegex = "foo."; assertEquals(true, strInput.matches(strRegex)); }
Вы можете задаться вопросом, почему совпадение успешно, если во входной строке нет символа точки (.)?
Ответ прост. Точка (.) является метасимволом – особое значение точки здесь заключается в том, что на ее месте может быть “любой символ”. Таким образом, ясно, как совпадения определили, что совпадение найдено.
Допустим, мы не хотим рассматривать символ точки (.) с его уникальным значением. Вместо этого мы хотим, чтобы он интерпретировался как знак точки. Это означает, что в предыдущем примере мы не хотим позволять шаблону foo. чтобы иметь совпадение во входной строке .
Как бы мы справились с подобной ситуацией? Ответ таков: нам нужно избежать символа точки (.), чтобы его особое значение было проигнорировано.
Давайте рассмотрим это более подробно в следующем разделе.
3. Экранирование Символов
Согласно документации Java API для регулярных выражений, существует два способа, с помощью которых мы можем экранировать символы, имеющие особое значение. Другими словами, заставить относиться к ним как к обычным персонажам.
Давайте посмотрим, что это такое:
- Предшествуйте метасимволу с обратной косой чертой (\)
- Заключите метасимвол с \Q и \E
Это просто означает, что в примере, который мы видели ранее, если мы хотим избежать символа точки, нам нужно поставить символ обратной косой черты перед символом точки. В качестве альтернативы мы можем поместить символ точки между \Q и \E.
3.1. Экранирование С Помощью Обратной Косой Черты
Это один из методов, которые мы можем использовать, чтобы избежать метасимволов в регулярном выражении. Однако мы знаем, что символ обратной косой черты также является escape-символом в литералах Java String . Поэтому нам нужно удвоить символ обратной косой черты при его использовании, чтобы предшествовать любому символу (включая сам символ\).
Следовательно, в нашем примере нам нужно изменить регулярное выражение, как показано в этом тесте:
@Test public void givenRegexWithDotEsc_whenMatchingStr_thenNotMatching() { String strInput = "foof"; String strRegex = "foo\\."; assertEquals(false, strInput.matches(strRegex)); }
Здесь символ точки экранируется, поэтому совпадения просто рассматривают его как собаку и пытаются найти шаблон, который заканчивается точкой (т. Е. foo. ).
В этом случае он возвращает false , так как во входных данных String для этого шаблона нет совпадения.
3.2. Побег с помощью \Q & \E
В качестве альтернативы мы можем использовать \Q и \E , чтобы избежать специального символа. \Q указывает, что все символы до \E должны быть экранированы, а \E означает, что нам нужно завершить экранирование, которое было начато с \Q .
Это просто означает, что все, что находится между \Q и \E , будет экранировано.
В тесте, показанном здесь, split() класса String выполняет сопоставление с использованием предоставленного ему регулярного выражения.
Наше требование состоит в том, чтобы разделить входную строку символом канала (|) на слова. Поэтому для этого мы используем шаблон регулярного выражения.
Символ канала-это метасимвол, который необходимо экранировать в регулярном выражении.
Здесь экранирование выполняется путем размещения символа канала между \Q и \E :
@Test public void givenRegexWithPipeEscaped_whenSplitStr_thenSplits() { String strInput = "foo|bar|hello|world"; String strRegex = "\\Q|\\E"; assertEquals(4, strInput.split(strRegex).length); }
4. Метод Pattern.quote(String S)
Метод Pattern.Quote(String S) в классе java.util.regex.Pattern преобразует заданный шаблон регулярного выражения String в литеральный шаблон String. Это означает, что все метасимволы во входной строке | рассматриваются как обычные символы.
Использование этого метода было бы более удобной альтернативой, чем использование \Q & \E , поскольку он обертывает заданную строку с ними.
Давайте посмотрим на этот метод в действии:
@Test public void givenRegexWithPipeEscQuoteMeth_whenSplitStr_thenSplits() { String strInput = "foo|bar|hello|world"; String strRegex = "|"; assertEquals(4,strInput.split(Pattern.quote(strRegex)).length); }
В этом быстром тесте метод Pattern.quote() используется для экранирования заданного шаблона регулярного выражения и преобразования его в литерал String . Другими словами, он ускользает от всех метасимволов, присутствующих в шаблоне регулярных выражений для нас. Он выполняет аналогичную работу с \Q & \E .
Символ канала экранируется методом Pattern.quote() , а split() интерпретирует его как String литерал, на который он делит входные данные.
Как мы видим, это гораздо более чистый подход, а также разработчикам не нужно запоминать все escape-последовательности.
Следует отметить, что Следует отметить, что заключает весь блок в единственную escape-последовательность. Если бы мы хотели экранировать символы по отдельности, нам нужно было бы использовать алгоритм замены токенов .
5. Дополнительные Примеры
Давайте посмотрим, как replaceAll() метод java.util.regex.Матчер труды.
Если нам нужно заменить все вхождения данного символа String другим, мы можем использовать этот метод, передав ему регулярное выражение.
Представьте, что у нас есть входные данные с несколькими вхождениями символа $ . Результат, который мы хотим получить, – это та же строка с символом $ , замененным на £.
Этот тест демонстрирует, как шаблон $ передается без экранирования:
@Test public void givenRegexWithDollar_whenReplacing_thenNotReplace() { String strInput = "I gave $50 to my brother." + "He bought candy for $35. Now he has $15 left."; String strRegex = "$"; String strReplacement = "£"; String output = "I gave £50 to my brother." + "He bought candy for £35. Now he has £15 left."; Pattern p = Pattern.compile(strRegex); Matcher m = p.matcher(strInput); assertThat(output, not(equalTo(m.replaceAll(strReplacement)))); }
Тест утверждает, что $ неправильно заменен на £ .
Теперь, если мы избегаем шаблона регулярных выражений, замена происходит правильно, и тест проходит, как показано в этом фрагменте кода:
@Test public void givenRegexWithDollarEsc_whenReplacing_thenReplace() { String strInput = "I gave $50 to my brother." + "He bought candy for $35. Now he has $15 left."; String strRegex = "\\$"; String strReplacement = "£"; String output = "I gave £50 to my brother." + "He bought candy for £35. Now he has £15 left."; Pattern p = Pattern.compile(strRegex); Matcher m = p.matcher(strInput); assertEquals(output,m.replaceAll(strReplacement)); }
Обратите внимание на \\$ здесь, который делает трюк, экранируя символ $ и успешно сопоставляя шаблон.
6. Заключение
В этой статье мы рассмотрели экранирование символов в регулярных выражениях в Java.
Мы обсудили, почему регулярные выражения должны быть экранированы, и различные способы, которыми это может быть достигнуто.
Как всегда, исходный код, связанный с этой статьей, можно найти на GitHub .