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

TL; DR

Каковы проектные решения API-интерфейса Matcher ?

Задний план

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

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

Дальнейшее описание документации API:

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

пример

 String s = "foo=23,bar=42"; Pattern p = Pattern.compile("foo=(?[0-9]*),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 

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

 String s = "foo=23,bar=42"; Pattern p = Pattern.compile("foo=(?[0-9]*),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) с помощью Patter.matcher(String) ?

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

пытаясь запросить любую его часть до успешного совпадения, будет выведено исключение 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 проверка не сможет этого сделать. Для этого вам нужно будет переместить ваш матчи вперед, чтобы соответствовать следующей строке. Таким образом, для выполнения этой цели существуют различные методы, определенные в classе Matcher . Метод matcher.find() соответствует matcher.find() до тех пор, пока все совпадения не будут найдены.

Существуют и другие методы, которые match строке по-другому, что зависит от того, как вы хотите сопоставить. Таким образом, в конечном итоге на classе Matcher выполняется matching со строкой. Класс Pattern просто создает pattern для сопоставления. Если Pattern.matcher() должны match шаблону, тогда должен быть какой-то способ определить различные способы match , поскольку matching может быть по-разному. Таким образом, возникает необходимость в classе 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 совпадения, ваш первый способ будет печатать только первый, тогда как второй способ будет распечатывать все совпадения, перемещая матчи вперед, чтобы соответствовать следующему шаблону.

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

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

Соединитель создается из шаблона, вызывая метод сопоставления шаблона. После создания сопоставления можно использовать для выполнения трех различных операций совпадения:

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

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

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

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

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

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

Линия:

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

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

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

Линии:

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

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

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

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

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

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

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

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

     Pattern p = new Pattern("([az]=([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 на шаблоне

  2. Разработчик может использовать RE как lexer, и разработчик может привязывать лексические маркеры к парсеру . На практике это будет работать для простых доменных языков, но регулярные выражения, вероятно, не подходят для полномасштабного компьютерного языка. EDIT. Это частично связано с предыдущей причиной, но часто бывает проще и эффективнее создавать дерево parsingа, обрабатывая текст, чем сначала лексировать все входные данные.
  3. (Для храбрых) вы можете отлаживать REs и выяснить, какая подпоследовательность не соответствует (или неправильно соответствует).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Перевести регулярные выражения Perl на .NET.
  • Java-Извлечение части строки между двумя специальными символами
  • Получение текста после соответствия регулярному выражению
  • Как извлечь подстроку с помощью регулярного выражения
  • Разделить регулярное выражение для извлечения строк непрерывных символов
  • Как использовать '-prune' вариант 'find' в sh?
  • Получение частей URL (регулярное выражение)
  • Найти и заменить {0,} вхождения в Word
  • Наглядно ли это влияет на то, какие языки могут быть сопоставлены регулярными выражениями?
  • Есть ли команда linux, например mv, но с регулярным выражением?
  • В чем сложность регулярного выражения?
  • Interesting Posts

    Получается ли этот тип памяти в куче или стеке?

    Idiomatic R-код для разбиения вектора по индексу и выполнения операции над этим разделом

    Пакетное изменение кодировки файлов ascii с utf-8 на iso-8859-1

    Как оживить изменение изображения в UIImageView?

    Нет отображения после CMOS и сброса BIOS

    Clojure: уменьшить или применить

    Использование JSLint с Syntastic в Vim

    Сертификат безопасности сайта подписан с использованием слабого алгоритма подписи! Не может получить доступ к сайту HTTPS

    Мой жесткий диск не прошел проверку SMART и самодиагностику с коротким приводом. Что мне делать?

    Какие параметры командной строки доступны в Adobe Acrobat (не Reader) для изменения документов (Convert, Merge и т. Д.)?

    Сколько времени потребуется, чтобы сократить количество разделов на 44 ГБ до 10 ГБ

    Что означает этот код C # со «стрелкой» и как он называется?

    Как я могу получить расположение файловой системы сценария PowerShell?

    Не отключены расширения Firefox, что делает Firefox медленнее?

    Как я могу изменить содержимое файла ISO?

    Давайте будем гением компьютера.