Matcher.find(): примеры (JAVA)

Примеры применения 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).

Пример java
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() для ограничения области поиска.

Пример java
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).

Пример java
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+).

Пример java
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.

Пример java
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).

Пример java
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) Пример сложной фильтрации: извлечение именованных групп и их индексов.

Пример java
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

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

джава Matcher.find() function comments

En
Matcher.find() Ищет следующее соответствие шаблону в строке