Matcher.find(): примеры (JAVA)
Matcher.find(): booleanОбщее описание метода Matcher.find()
Метод Matcher.find() из пакета java.util.regex выполняет поиск следующего подпоследовательного совпадения с регулярным выражением, связанным с объектом Matcher. Существует две перегруженные формы:
boolean find()- ищет следующее совпадение, начиная с текущей позиции поиска.boolean find(int start)- начинает поиск с указанного индексаstartв исходной строке.
Результат - логическое значение: true, если найдено совпадение, и false, если совпадений больше нет. После успешного вызова find() доступны методы start(), end() и group() для получения границ и содержимого совпадения.
Ключевые особенности и правила поведения:
- При каждом успешном вызове
find()позиция поиска продвигается за найденное совпадение. При повторных вызовах происходит последовательный поиск следующих непересекающихся совпадений, если шаблон не допускает нулевой длины совпадения. - Если найдено совпадение нулевой длины (length == 0), реализация продвигает позицию поиска на единицу, чтобы избежать бесконечного цикла при последующих вызовах
find(). Это влияет на поиск смежных нулевых совпадений. find(int start)начинает новый поиск с позицииstart. Еслиstartменьше нуля или больше длины исходной строки, будет выброшеноIndexOutOfBoundsException.- Если до вызова
find()не было успешного совпадения, вызовыgroup(),start(),end()приведут кIllegalStateException. - Метод
find()не требует совпадения по всей строке. Для проверки полного соответствия применяетсяmatches(), а для проверки в начале -lookingAt(). - Регион и флаги
useTransparentBoundsиuseAnchoringBoundsвлияют на область поиска и поведение якорей, аregion(int start, int end)ограничивает границы, в которых выполняютсяfind().
Базовые примеры использования
Примеры демонстрируют привычные варианты вызова метода и ожидаемые результаты.
1) Поиск всех групп цифр в строке.
import java.util.regex.*;
public class Ex1 {
public static void main(String[] args) {
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("a12b345c6");
while (m.find()) {
System.out.println(m.group() + " at " + m.start() + ".." + m.end());
}
}
}
12 at 1..3 345 at 3..6 6 at 6..7
2) Поиск с указанием начальной позиции.
Pattern p = Pattern.compile("ab");
Matcher m = p.matcher("zabxab");
boolean b1 = m.find(1); // старт с позиции 1
boolean b2 = m.find(2); // старт с позиции 2
System.out.println(b1 + ", " + b2 + ", pos: " + (b2 ? m.start() : -1));
true, true, pos: 4
3) Поведение при нулевой длине совпадения.
Pattern p = Pattern.compile("(?=a)"); // позитивный просмотр вперед, нулевая длина
Matcher m = p.matcher("baaa");
while (m.find()) {
System.out.println("match at " + m.start() + ".." + m.end());
}
match at 1..1 match at 2..2 match at 3..3
Похожие API в Java и их отличия
- matches() - требует, чтобы весь регион совпадал с шаблоном. Предпочтительнее при проверке полного соответствия, а не поиска фрагментов.
- lookingAt() - проверяет соответствие в начале текущей позиции региона. Полезно при необходимости убедиться, что шаблон начинается в текущей позиции без требования полного совпадения всей строки.
- Pattern.split() - разделяет строку по шаблону. Удобно для разбиения, но не для получения позиций совпадений.
- Matcher.results() (Java 9+) - возвращает поток результатов сопоставления (
Stream). Удобно для функциональной обработки всех совпадений без явного циклаwhile (find()).
Аналоги в других языках и их особенности
Краткие примеры показывают эквивалентные подходы и отличия в API.
PHP - preg_match_all для всех совпадений:
// PHP
$subject = 'a12b345c6';
preg_match_all('/\\d+/', $subject, $matches, PREG_OFFSET_CAPTURE);
print_r($matches[0]);
Array
(
[0] => Array
(
[0] => 12
[1] => 1
)
[1] => Array
(
[0] => 345
[1] => 3
)
[2] => Array
(
[0] => 6
[1] => 6
)
)
JavaScript - RegExp.prototype.exec или String.prototype.matchAll:
// JavaScript
const re = /\d+/g;
let s = 'a12b345c6';
let m;
while ((m = re.exec(s)) !== null) {
console.log(m[0], m.index);
}
12 1 345 3 6 6
Python - re.finditer возвращает итератор совпадений:
# Python
import re
s = 'a12b345c6'
for m in re.finditer(r'\d+', s):
print(m.group(), m.start(), m.end())
12 1 3 345 3 6 6 6 7
C# - Regex.Matches или Regex.Match с циклом:
// C#
using System.Text.RegularExpressions;
var m = Regex.Matches("a12b345c6", "\\d+");
foreach (Match x in m) Console.WriteLine($"{x.Value} at {x.Index}");
12 at 1 345 at 3 6 at 6
Go - пакет regexp:
// Go
import (
"fmt"
"regexp"
)
r := regexp.MustCompile(`\\d+`)
fmt.Println(r.FindAllStringIndex("a12b345c6", -1))
[[1 3] [3 6] [6 7]]
Kotlin - класс Regex с методом findAll:
// Kotlin
val re = Regex("\\d+")
val s = "a12b345c6"
re.findAll(s).forEach { println("${it.value} at ${it.range.first}") }
12 at 1 345 at 3 6 at 6
Отличия от Java:
- Некоторые языки возвращают массив всех совпадений сразу (
preg_match_all,Regex.Matches), другие - итератор/поток (finditer,matchAll,Matcher.find()с циклом). - Механика нулевой длины и продвижения позиции зависят от реализации, поэтому для перекрывающихся совпадений чаще используются фиктивные конструкции или просмотр вперед (lookahead).
Типичные ошибки и примеры
Ниже распространенные ошибки при работе с find() и результаты их проявления.
1) Вызов group() без успешного совпадения.
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("abc");
// Без вызова find()
try {
System.out.println(m.group());
} catch (IllegalStateException e) {
System.out.println("IllegalStateException: " + e.getMessage());
}
IllegalStateException: No match found
2) start() вне диапазона при find(int) с некорректным индексом.
Pattern p = Pattern.compile("a");
Matcher m = p.matcher("abc");
try {
m.find(10);
} catch (IndexOutOfBoundsException e) {
System.out.println("IndexOutOfBoundsException: " + e.getMessage());
}
IndexOutOfBoundsException: From index out of range: 10
3) Ожидание перекрывающихся совпадений без специальных приемов.
Pattern p = Pattern.compile("aba");
Matcher m = p.matcher("ababa");
while (m.find()) System.out.println(m.start());
// Ожидаем 0 и 2, но получим только 0 при стандартном поведении
0
Пояснение: стандартный find() находит непересекающиеся совпадения; для перекрывающихся требуется lookahead или ручное управление позицией.
4) Неправильное экранирование регулярных конструкций в строковых литералах Java.
// Ошибка: строка "\d+" в исходном коде должна быть написана "\\d+"
Pattern p = Pattern.compile("\d+"); // синтаксическая ошибка/неверный шаблон
PatternSyntaxException или неожиданные результаты при запуске
Изменения и связанные новшества в JDK
Сам метод Matcher.find() в Java исторически стабильный и принципиально не менял семантику в последних релизах. Однако рядом с ним появились удобные дополнения:
- Matcher.results() (введен в Java 9) - предоставляет
Stream<MatchResult>для функциональной обработки всех совпадений. - Улучшения производительности и исправления в движке регулярных выражений в разных релизах JDK, но поведение API по контракту осталось совместимо.
Расширенные и редкие сценарии использования
Подробные примеры с пояснениями.
1) Поиск перекрывающихся совпадений с помощью позитивного просмотра вперед (lookahead).
Pattern p = Pattern.compile("(?=(aba))");
Matcher m = p.matcher("ababa");
while (m.find()) {
System.out.println(m.group(1) + " at " + m.start());
}
aba at 0 aba at 2
Пояснение: сам внешний матч нулевой длины, а захваченная группа 1 содержит желаемый фрагмент.
2) Использование region() для ограничения области поиска.
Pattern p = Pattern.compile("\\w+");
Matcher m = p.matcher("one two three");
// искать только в подстроке "two"
m.region(4, 7);
while (m.find()) System.out.println(m.group() + " at " + m.start());
two at 4
3) Замены по найденным фрагментам с сохранением остального текста (appendReplacement/appendTail).
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("a12b345c6");
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, "X");
}
m.appendTail(sb);
System.out.println(sb.toString());
aXbXcX
Пояснение: appendReplacement позволяет аккуратно заменять совпадения, сохраняя промежуточный контент.
4) Обработка совпадений как потока (Java 9+).
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("a12b345c6");
// Получение Stream
p.results().map(r -> r.group()).forEach(System.out::println);
12 345 6
5) Поиск с контролем границ якорей: useAnchoringBounds/useTransparentBounds.
Pattern p = Pattern.compile("^foo");
Matcher m = p.matcher("barfoo");
// По умолчанию ^ привязан к началу региона; можно изменить поведение через region и флаги
m.region(3, 6); // регион на "foo"
System.out.println(m.lookingAt());
true
6) Ручная эмуляция перекрывающегося поиска без lookahead: контроль индекса в find(int).
Pattern p = Pattern.compile("aba");
Matcher m = p.matcher("ababa");
int pos = 0;
while (pos <= m.regionEnd()) {
if (m.find(pos)) {
System.out.println("found at " + m.start());
pos = m.start() + 1; // шаг на 1 для поиска перекрытий
} else break;
}
found at 0 found at 2
7) Пример сложной фильтрации: извлечение именованных групп и их индексов.
Pattern p = Pattern.compile("(?[A-Z][a-z]+) (?:age=(?\\d+))?");
Matcher m = p.matcher("Alice age=30 Bob age=25 Carol");
while (m.find()) {
System.out.println("name=" + m.group("name") + ", age=" + m.group("age") + " at " + m.start());
}
name=Alice, age=30 at 0 name=Bob, age=25 at 11 name=Carol, age=null at 21
Пояснение: именованные группы упрощают разбор сложных шаблонов и делают код более читабельным.