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

#java #android #regex #android-studio

#java #регулярное выражение #исключение illegalstateexception

Вопрос:

TL; DR

Каковы проектные решения, лежащие Matcher в основе API?

Предыстория

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

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

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

Явное состояние сопоставителя изначально не определено; попытка запросить какую-либо его часть до успешного сопоставления приведет к возникновению исключения 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
  

в (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() at (2) переводит Matcher в надлежащее состояние для вызова group() .

Вопрос, вероятно, не конструктивный

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

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

1. Интересно. Даже через несколько недель после того, как вопрос, ответы и вознаграждение урегулированы, люди считают, что этот вопрос стоит понижения.

2. Я считаю, что это плохой дизайн API — в некоторой степени эквивалентно требованию своего рода ‘initialize ()’ после построения. Существуют законные ситуации, в которые вы можете попасть, когда вы знаете, что данный сопоставитель уже соответствует заданной строке.

3. @Ben Худший дизайн API, который я когда-либо видел в фреймворке! Очень неинтуитивно с неинформативным бесполезным сообщением об ошибке:- (

Ответ №1:

На самом деле, вы неправильно поняли документацию. Взгляните на приведенное вами утверждение 2 раза: —

попытка запросить любую его часть до успешного сопоставления приведет к возникновению исключения 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());
}
  

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

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

Я надеюсь, что это проясняет.

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

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

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

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

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

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

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

1. Да, это то, что говорится в документации. Я это знаю. Мой (не конструктивный) вопрос: почему этот API разработан таким образом?

2. @Tichodroma На этот вопрос трудно дать объективный ответ. Всегда есть несколько способов что-то спроектировать. Только человек, который это изобрел, может объяснить, почему он или она выбрали это решение.

3. @Tichodroma Это потому, что, пока вы match не создадите шаблон с определенной строкой. Он не будет содержать никаких данных, которые соответствовали. Это просто: — You cannot access a string that has not been matched yet.

4. @Tichodroma По сути, matcher.group() tries возвращает match то, что было найдено в текущей строке. Но вы должны initiate выполнить операцию сопоставления. Это то, что matcher.matches() делает.

5. @Tichodroma. Вы поняли? Или вы все еще в замешательстве. Вы не можете получить строку, которая не была сопоставлена шаблоном. Вот так просто.

Ответ №2:

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

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

Строка:

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

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

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

Строки:

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

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

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

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

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

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

Почему кто-то хочет выполнить пошаговый синтаксический анализ?

  1. Получение значений из групп, которые имеют количественную оценку больше 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 по шаблону

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

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

Ответ №3:

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

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

Ответ №4:

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

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

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

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

Ответ №5:

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

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

Давайте сначала рассмотрим group() . Если мы не смогли что-то успешно сопоставить, сопоставитель не должен возвращать пустую строку, поскольку она не соответствует пустой строке. 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 . Это именно то, что говорится в документации:

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

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