Рубрики
Без рубрики

Руководство по экранированию символов в регулярном выражении Java

Узнайте, как экранировать специальные символы в регулярных выражениях Java.

Автор оригинала: baeldung.

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 для регулярных выражений, существует два способа, с помощью которых мы можем экранировать символы, имеющие особое значение. Другими словами, заставить относиться к ним как к обычным персонажам.

Давайте посмотрим, что это такое:

  1. Предшествуйте метасимволу с обратной косой чертой (\)
  2. Заключите метасимвол с \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 .