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

Обоснование для Матчи, бросающего IllegalStateException, когда метод 'matching' не вызван

TL; DR

Каковы проектные решения для Matcher API?

Фон

Matcher имеет поведение, которого я не ожидал, и для которого я не могу найти вескую причину. В документации по API говорится:

После создания соединитель можно использовать для выполнения трех различных операций совпадения: [...] Каждый из этих методов возвращает логическое значение, указывающее на успех или неудачу. Более подробную информацию об успешном совпадении можно получить, запросив состояние совпадения.

В документации API далее говорится:

Явное состояние совпадения первоначально undefined; пытаясь запросить любую его часть перед успешным совпадением, будет выведено исключение IllegalStateException.

Пример

String s = "foo=23,bar=42";
Pattern p = Pattern.compile("foo=(?<foo>[0-9]*),bar=(?<bar>[0-9]*)");
Matcher matcher = p.matcher(s);
System.out.println(matcher.group("foo")); // (1)
System.out.println(matcher.group("bar"));

Этот код выдает

java.lang.IllegalStateException: No match found

at (1). Чтобы обойти это, необходимо вызвать matches() или другие методы, которые приносят Matcher в состояние, которое позволяет group(). Следующие работы:

String s = "foo=23,bar=42";
Pattern p = Pattern.compile("foo=(?<foo>[0-9]*),bar=(?<bar>[0-9]*)");
Matcher matcher = p.matcher(s);
matcher.matches(); // (2)
System.out.println(matcher.group("foo"));
System.out.println(matcher.group("bar"));

Добавление вызова matches() в (2) устанавливает Matcher в правильное состояние для вызова group().

Вопрос, возможно, не конструктивный

Почему этот API разработан так? Почему автоматическое совпадение при построении Matcher с помощью Patter.matcher(String)?

4b9b3361

Ответ 1

Собственно, вы неправильно поняли документацию. Сделайте второй обзор заявления, которое вы указали: -

попытка запросить любую ее часть до успешного совпадения приведет к Исключение IllegalStateException.

Соединитель может бросать IllegalStateException при доступе к matcher.group(), если совпадение не найдено.

Итак, вам нужно использовать следующий тест, чтобы фактически инициировать процесс сопоставления: -

 - matcher.matches() //Or
 - matcher.find()

Следующий код: -

Matcher matcher = pattern.matcher();  

Просто создает экземпляр matcher. Это не будет соответствовать строке. Даже если был успешный матч. Итак, вам нужно проверить следующее условие, чтобы проверить успешные совпадения: -

if (matcher.matches()) {
    // Then use `matcher.group()`
}

И если условие в if возвращает false, это означает, что ничего не было сопоставлено. Итак, если вы используете matcher.group(), не проверяя это условие, вы получите IllegalStateException, если совпадение не было найдено.


Предположим, что если matcher был спроектирован так, как вы говорите, тогда вам нужно будет сделать проверку null, чтобы проверить, было ли найдено совпадение, для вызова matcher.group(), например: -

Как вы думаете, должно было быть сделано: -

// Suppose this returned the matched string
Matcher matcher = pattern.matcher(s);  

// Need to check whether there was actually a match
if (matcher != null) {  // Prints only the first match

    System.out.println(matcher.group());
}

Но что, если вы хотите напечатать какие-либо дополнительные совпадения, так как шаблон может быть сопоставлен несколько раз в String, для этого должен быть способ сообщить совпадению найти следующее совпадение. Но проверка null не сможет этого сделать. Для этого вам нужно будет переместить ваш матчи вперед, чтобы соответствовать следующей строке. Таким образом, для выполнения этой цели существуют различные методы, определенные в классе matcher. Метод matcher.find() соответствует строке до тех пор, пока не будет найдено совпадение.

Существуют и другие методы, что match строка по-другому, что зависит от вас, как вы хотите сопоставить. Итак, в конечном итоге на matcher класс сделать matching для строки. Pattern класс просто создает Pattern для сопоставления. Если Pattern.matcher() соответствует match шаблону, тогда должен быть некоторый способ определить различные способы match, так как matching может быть по-разному. Итак, возникает необходимость в классе matcher.

Итак, так оно и есть: -

Matcher matcher = pattern.matcher(s);

   // Finds all the matches until found by moving the `matcher` forward
while(matcher.find()) {
    System.out.println(matcher.group());
}

Итак, если в строке найдено 4 совпадения, ваш первый способ будет печатать только первый, а второй способ распечатает все совпадения, перемещая matcher вперед, чтобы соответствовать следующему шаблону.

Надеюсь, что это ясно.

Документация класса matcher описывает использование трех методов, которые он предоставляет, в котором говорится: -

Матчи создаются из шаблона, вызывая совпадение шаблонов метод. После того, как он создан, матчи могут использоваться для выполнения трех разных виды совпадений:

  • Метод совпадений пытается совместить всю входную последовательность против шаблона.

  • Метод lookAt пытается совместить входную последовательность, начиная в начале, против шаблона.

  • Метод поиска сканирует входную последовательность, ища следующую подпоследовательность, которая соответствует шаблону.

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

Ответ 2

Мой ответ очень похож на Rohit Jain, но включает в себя некоторые причины, по которым необходим "дополнительный" шаг.

реализация java.util.regex

Строка:

Pattern p = Pattern.compile("foo=(?<foo>[0-9]*),bar=(?<bar>[0-9]*)");

вызывает выделение нового объекта шаблона, и он внутренне сохраняет структуру, представляющую RE-информацию, такую ​​как выбор символов, групп, последовательностей, жадных против неживых, повторений и т.д.

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

Строки:

String s = "foo=23,bar=42";
Matcher matcher = p.matcher(s);

возвращает новый объект Matcher для Pattern и String - тот, который еще не прочитал строку. Matcher - это действительно состояние состояния конечной машины, где конечным автоматом является Pattern.

Согласование может выполняться путем перехода на конечный автомат через процесс сопоставления с использованием следующего API:

  • lookingAt(): пытается совместить входную последовательность, начиная с начала, с шаблоном
  • find(): сканирует входную последовательность, ища следующую подпоследовательность, которая соответствует шаблону.

В обоих случаях промежуточное состояние может быть прочитано с использованием методов start(), end() и group().

Преимущества такого подхода

Почему кто-то хочет сделать шаг в синтаксическом разборе?

  • Получить значения из групп с квантификацией больше 1 (т.е. групп, которые повторяются и заканчиваются совпадением более одного раза). Например, в тривиальном RE ниже, который анализирует присваивания переменных:

    Pattern p = new Pattern("([a-z]=([0-9]+);)+");
    Matcher m = p.matcher("a=1;b=2;x=3;");
    m.matches();
    System.out.println(m.group(2)); // Only matches value for x ('3') - not the other values
    

    См. раздел "Имя группы" в разделе "Группы и захват" JavaDoc на Pattern

  • Разработчик может использовать RE как lexer, и разработчик может привязать лексические токены к parser. На практике это будет работать для простых доменных языков, но регулярные выражения, вероятно, не подходят для полномасштабного компьютерного языка. РЕДАКТИРОВАТЬ Отчасти это связано с предыдущей причиной, но часто бывает проще и эффективнее создавать дерево разбора, обрабатывая текст, чем сначала лексировать все входные данные.
  • (Для храбрых) вы можете отлаживать RE и выяснять, какая подпоследовательность не соответствует (или некорректно соответствует).

Однако в большинстве случаев вам не нужно набирать конечный автомат с помощью сопоставления, поэтому есть метод удобства (matches), который запускает соответствие шаблона для завершения.

Ответ 3

Если совпадение автоматически соответствует входной строке, это будет потраченное впустую усилие, если вы хотите найти шаблон.. p >

Матчи могут использоваться для проверки того, является ли шаблон matches() входной строкой, и его можно использовать для find() шаблона во входной строке (даже неоднократно, чтобы найти все соответствующие подстроки). Пока вы не назовете один из этих двух методов, помощник не знает, какой тест вы хотите выполнить, поэтому он не может дать вам никаких согласованных групп. Даже если вы вызываете один из этих методов, вызов может выйти из строя - шаблон не найден - и в этом случае также должен завершиться вызов group.

Ответ 4

Это ожидается и задокументировано.

Причина в том, что .matches() возвращает логическое значение, указывающее, было ли совпадение. Если было совпадение, вы можете называть .group(...) значимым. В противном случае, если нет совпадения, вызов .group(...) не имеет смысла. Поэтому вам не разрешается вызывать .group(...) перед вызовом matches().

Правильный способ использования соединителя - это что-то вроде следующего:

Matcher m = p.matcher(s);
if (m.matches()) {
  ...println(matcher.group("foo"));
  ...
}

Ответ 5

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

Рассмотрим это: что вы ожидаете, что запросы Matcher вернутся, если совпадение не удалось каким-то образом сопоставить?

Сначала рассмотрим group(). Если мы не успели что-то сопоставить, Matcher не должен возвращать пустую строку, поскольку она не соответствует пустой строке. Мы могли бы вернуть null в этот момент.

Хорошо, теперь рассмотрим start() и end(). Каждое возвращение int. Какое значение int было бы в этом случае? Конечно, нет положительного числа. Какое отрицательное число было бы уместным? -1?

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

Я дам, что повторное использование IllegalStateException, возможно, не привело к лучшему описанию условия ошибки. Но если мы должны были переименовать/подклассы IllegalStateException в NoSuccessfulMatchException, нужно быть в состоянии оценить, как текущий дизайн обеспечивает согласованность запросов и побуждает пользователя использовать запросы, которые имеют семантику, которые, как известно, определены во время запроса.

TL; DR: Какова ценность запроса конкретной причины смерти живого организма?

Ответ 6

Вам нужно проверить возвращаемое значение matcher.matches(). Он вернет true, когда совпадение найдено, false в противном случае.

if (matcher.matches()) {
    System.out.println(matcher.group("foo"));
    System.out.println(matcher.group("bar"));
}

Если matcher.matches() не находит совпадения, и вы вызываете matcher.group(...), вы все равно получите IllegalStateException. Вот что говорит документация:

Явное состояние совпадения первоначально undefined; пытаясь запросить любую его часть перед успешным совпадением, будет выведено исключение IllegalStateException.

Когда matcher.match() возвращает false, успешное совпадение не найдено, и нет смысла получать информацию о совпадении, вызывая, например, group().