1. Обзор
В этой статье мы обсудим API регулярных выражений Java и то, как регулярные выражения могут использоваться в языке программирования Java.
В мире регулярных выражений существует множество различных вариантов на выбор, таких как grep, Perl, Python, PHP, awk и многое другое.
Это означает, что регулярное выражение, которое работает на одном языке программирования, может не работать на другом. Синтаксис регулярных выражений в Java наиболее похож на синтаксис Perl.
2. Настройка
Чтобы использовать регулярные выражения в Java, нам не нужна специальная настройка. JDK содержит специальный пакет java.util.regex , полностью посвященный операциям с регулярными выражениями. Нам нужно только импортировать его в наш код.
Более того, java.lang.Класс String также имеет встроенную поддержку регулярных выражений, которую мы обычно используем в нашем коде.
3. Пакет регулярных выражений Java
Пакет java.util.regex состоит из трех классов: Pattern, Matcher и PatternSyntaxException :
- Pattern object-это скомпилированное регулярное выражение. Класс Pattern не предоставляет общедоступных конструкторов. Чтобы создать шаблон, мы должны сначала вызвать один из его общедоступных статических методов compile , который затем вернет объект Pattern . Эти методы принимают регулярное выражение в качестве первого аргумента.
- Сопоставитель объект интерпретирует шаблон и выполняет операции сопоставления с входной строкой . Он также не определяет публичных конструкторов. Мы получаем объект Matcher , вызывая метод matches для объекта Pattern .
- PatternSyntaxException объект-это непроверенное исключение, указывающее на синтаксическую ошибку в шаблоне регулярного выражения.
Мы подробно рассмотрим эти классы; однако сначала мы должны понять, как создается регулярное выражение в Java.
Если вы уже знакомы с регулярными выражениями из другой среды, вы можете обнаружить определенные различия, но они минимальны.
4. Простой Пример
Давайте начнем с самого простого варианта использования регулярного выражения. Как мы уже отмечали ранее, когда регулярное выражение применяется к строке, оно может совпадать с нулем или более раз.
Наиболее простой формой сопоставления шаблонов, поддерживаемой java.util.regex API, является сопоставление строки литерала . Например, если регулярное выражение foo и входная Строка является foo , совпадение будет успешным, потому что Строки идентичны:
@Test
public void givenText_whenSimpleRegexMatches_thenCorrect() {
Pattern pattern = Pattern.compile("foo");
Matcher matcher = pattern.matcher("foo");
assertTrue(matcher.find());
}Сначала мы создаем объект Pattern , вызывая его статический метод compile и передавая ему шаблон, который мы хотим использовать.
Затем мы создаем объект Matcher , вызывая метод Pattern объекта matcher и передавая ему текст, который мы хотим проверить на совпадения.
После этого мы вызываем метод find в объекте Matcher.
Метод find продолжает продвигаться по входному тексту и возвращает true для каждого совпадения, поэтому мы также можем использовать его для поиска количества совпадений:
@Test
public void givenText_whenSimpleRegexMatchesTwice_thenCorrect() {
Pattern pattern = Pattern.compile("foo");
Matcher matcher = pattern.matcher("foofoo");
int matches = 0;
while (matcher.find()) {
matches++;
}
assertEquals(matches, 2);
}Поскольку мы будем запускать больше тестов, мы можем абстрагировать логику поиска количества совпадений в методе под названием run Test :
public static int runTest(String regex, String text) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
int matches = 0;
while (matcher.find()) {
matches++;
}
return matches;
}Когда мы получаем 0 совпадений, тест должен провалиться, в противном случае он должен пройти.
5. Мета-символы
Метасимволы влияют на способ сопоставления шаблона, добавляя логику в шаблон поиска. Java API поддерживает несколько метасимволов, наиболее простым из которых является точка “.” , которая соответствует любому символу:
@Test
public void givenText_whenMatchesWithDotMetach_thenCorrect() {
int matches = runTest(".", "foo");
assertTrue(matches > 0);
}Учитывая предыдущий пример, где регулярное выражение foo соответствовало тексту foo , а также food два раза. Если бы мы использовали метасимвол точки в регулярном выражении, мы не получили бы двух совпадений во втором случае:
@Test
public void givenRepeatedText_whenMatchesOnceWithDotMetach_thenCorrect() {
int matches= runTest("foo.", "foofoo");
assertEquals(matches, 1);
}Обратите внимание на точку после foo в регулярном выражении. Сопоставитель соответствует каждому тексту, которому предшествует foo , поскольку последняя часть точки означает любой символ после. Таким образом , после нахождения первого для , остальные рассматриваются как любой символ. Вот почему существует только одно совпадение.
API поддерживает несколько других мета-символов <([{\^-=$!|]})?*+.> которые мы рассмотрим далее в этой статье.
6. Классы персонажей
Просматривая официальную спецификацию класса Pattern , мы обнаружим краткое описание поддерживаемых конструкций регулярных выражений. В классах символов у нас есть около 6 конструкций.
6.1. ИЛИ Класс
Построено как [abc] . Любой из элементов в наборе сопоставляется:
@Test
public void givenORSet_whenMatchesAny_thenCorrect() {
int matches = runTest("[abc]", "b");
assertEquals(matches, 1);
}Если все они присутствуют в тексте, каждый из них сопоставляется отдельно без учета порядка:
@Test
public void givenORSet_whenMatchesAnyAndAll_thenCorrect() {
int matches = runTest("[abc]", "cab");
assertEquals(matches, 3);
}Они также могут чередоваться как часть строки |. В следующем примере, когда мы создаем разные слова, чередуя первую букву с каждым элементом набора, все они совпадают:
@Test
public void givenORSet_whenMatchesAllCombinations_thenCorrect() {
int matches = runTest("[bcr]at", "bat cat rat");
assertEquals(matches, 3);
}6.2. НИ Класс
Вышеуказанный набор отрицается путем добавления каретки в качестве первого элемента:
@Test
public void givenNORSet_whenMatchesNon_thenCorrect() {
int matches = runTest("[^abc]", "g");
assertTrue(matches > 0);
}Еще один случай:
@Test
public void givenNORSet_whenMatchesAllExceptElements_thenCorrect() {
int matches = runTest("[^bcr]at", "sat mat eat");
assertTrue(matches > 0);
}6.3. Класс дальности
Мы можем определить класс, который определяет диапазон, в который должен попадать сопоставленный текст, используя дефис ( -), аналогично, мы также можем отрицать диапазон.
Соответствие прописных букв:
@Test
public void givenUpperCaseRange_whenMatchesUpperCase_
thenCorrect() {
int matches = runTest(
"[A-Z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 2);
}Соответствие строчных букв:
@Test
public void givenLowerCaseRange_whenMatchesLowerCase_
thenCorrect() {
int matches = runTest(
"[a-z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 26);
}Совпадение как прописных, так и строчных букв:
@Test
public void givenBothLowerAndUpperCaseRange_
whenMatchesAllLetters_thenCorrect() {
int matches = runTest(
"[a-zA-Z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 28);
}Соответствие заданному диапазону чисел:
@Test
public void givenNumberRange_whenMatchesAccurately_
thenCorrect() {
int matches = runTest(
"[1-5]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 2);
}Сопоставление другого диапазона чисел:
@Test
public void givenNumberRange_whenMatchesAccurately_
thenCorrect2(){
int matches = runTest(
"[30-35]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 1);
}6.4. Класс Объединения
Класс символов объединения является результатом объединения двух или более классов символов:
@Test
public void givenTwoSets_whenMatchesUnion_thenCorrect() {
int matches = runTest("[1-3[7-9]]", "123456789");
assertEquals(matches, 6);
}Приведенный выше тест будет соответствовать только 6 из 9 целых чисел, потому что набор объединения пропускает 4, 5 и 6.
6.5. Класс пересечения
Подобно классу объединения, этот класс является результатом выбора общих элементов между двумя или более наборами. Чтобы применить пересечение, мы используем && :
@Test
public void givenTwoSets_whenMatchesIntersection_thenCorrect() {
int matches = runTest("[1-6&&[3-9]]", "123456789");
assertEquals(matches, 4);
}Мы получаем 4 совпадения, потому что пересечение двух множеств имеет только 4 элемента.
6.6. Класс вычитания
Мы можем использовать вычитание для отрицания одного или нескольких классов символов, например, для сопоставления набора нечетных десятичных чисел:
@Test
public void givenSetWithSubtraction_whenMatchesAccurately_thenCorrect() {
int matches = runTest("[0-9&&[^2468]]", "123456789");
assertEquals(matches, 5);
}Только 1,3,5,7,9 будут совпадать.
7. Предопределенные Классы Символов
API регулярных выражений Java также принимает предопределенные классы символов. Некоторые из вышеперечисленных классов символов могут быть выражены в более короткой форме, хотя и делают код менее интуитивно понятным. Одним из особых аспектов Java-версии этого регулярного выражения является escape-символ.
Как мы увидим, большинство символов будут начинаться с обратной косой черты, которая имеет особое значение в Java. Чтобы они были скомпилированы классом Pattern – начальная обратная косая черта должна быть экранирована, т. е. \d становится \\d .
Совпадающие цифры, эквивалентные [0-9] :
@Test
public void givenDigits_whenMatches_thenCorrect() {
int matches = runTest("\\d", "123");
assertEquals(matches, 3);
}Совпадающие не-цифры, эквивалентные [^0-9] :
@Test
public void givenNonDigits_whenMatches_thenCorrect() {
int mathces = runTest("\\D", "a6c");
assertEquals(matches, 2);
}Соответствующие пробелы:
@Test
public void givenWhiteSpace_whenMatches_thenCorrect() {
int matches = runTest("\\s", "a c");
assertEquals(matches, 1);
}Соответствие небелому пространству:
@Test
public void givenNonWhiteSpace_whenMatches_thenCorrect() {
int matches = runTest("\\S", "a c");
assertEquals(matches, 2);
}Сопоставление символа слова, эквивалентного [a-zA-Z_0-9] :
@Test
public void givenWordCharacter_whenMatches_thenCorrect() {
int matches = runTest("\\w", "hi!");
assertEquals(matches, 2);
}Сопоставление несловесного символа:
@Test
public void givenNonWordCharacter_whenMatches_thenCorrect() {
int matches = runTest("\\W", "hi!");
assertEquals(matches, 1);
}8. Кванторы
API регулярных выражений Java также позволяет нам использовать кванторы. Они позволяют нам дополнительно настроить поведение совпадения, указав количество совпадений.
Чтобы сопоставить текст нулю или один раз, мы используем ? квантификатор:
@Test
public void givenZeroOrOneQuantifier_whenMatches_thenCorrect() {
int matches = runTest("\\a?", "hi");
assertEquals(matches, 3);
}В качестве альтернативы мы можем использовать синтаксис скобок, также поддерживаемый API регулярных выражений Java:
@Test
public void givenZeroOrOneQuantifier_whenMatches_thenCorrect2() {
int matches = runTest("\\a{0,1}", "hi");
assertEquals(matches, 3);
}В этом примере вводится понятие совпадений нулевой длины. Бывает так, что если количественный порог соответствия равен нулю, он всегда соответствует всему тексту, включая пустую строку | в конце каждого ввода. Это означает, что даже если входные данные пусты, они вернут одно совпадение нулевой длины.
Это объясняет, почему в приведенном выше примере мы получаем 3 совпадения, несмотря на то, что у нас есть строка длины два. Третье совпадение-пустая Строка нулевой длины .
Чтобы сопоставить текст нулю или неограниченному времени, мы используем квантор*, он просто похож на ?:
@Test
public void givenZeroOrManyQuantifier_whenMatches_thenCorrect() {
int matches = runTest("\\a*", "hi");
assertEquals(matches, 3);
}Поддерживаемая альтернатива:
@Test
public void givenZeroOrManyQuantifier_whenMatches_thenCorrect2() {
int matches = runTest("\\a{0,}", "hi");
assertEquals(matches, 3);
}Квантор с разницей равен +, он имеет порог соответствия 1. Если требуемая Строка вообще не встречается, совпадения не будет, даже строки нулевой длины |:
@Test
public void givenOneOrManyQuantifier_whenMatches_thenCorrect() {
int matches = runTest("\\a+", "hi");
assertFalse(matches);
}Поддерживаемая альтернатива:
@Test
public void givenOneOrManyQuantifier_whenMatches_thenCorrect2() {
int matches = runTest("\\a{1,}", "hi");
assertFalse(matches);
}Как и в Perl и других языках, синтаксис скобок может использоваться для сопоставления заданного текста несколько раз:
@Test
public void givenBraceQuantifier_whenMatches_thenCorrect() {
int matches = runTest("a{3}", "aaaaaa");
assertEquals(matches, 2);
}В приведенном выше примере мы получаем два совпадения, так как совпадение происходит только в том случае, если a появляется три раза подряд. Однако в следующем тесте мы не получим совпадения, так как текст появляется только два раза подряд:
@Test
public void givenBraceQuantifier_whenFailsToMatch_thenCorrect() {
int matches = runTest("a{3}", "aa");
assertFalse(matches > 0);
}Когда мы используем диапазон в скобке, совпадение будет жадным, совпадающим с более высоким концом диапазона:
@Test
public void givenBraceQuantifierWithRange_whenMatches_thenCorrect() {
int matches = runTest("a{2,3}", "aaaa");
assertEquals(matches, 1);
}Мы указали по крайней мере два вхождения, но не более трех, поэтому вместо этого мы получаем одно совпадение, в котором совпадения видят один aaa и a lone a, которые не могут быть сопоставлены.
Однако API позволяет нам указать ленивый или неохотный подход, так что сопоставитель может начинаться с нижнего конца диапазона, и в этом случае сопоставление двух вхождений как aa и aa :
@Test
public void givenBraceQuantifierWithRange_whenMatchesLazily_thenCorrect() {
int matches = runTest("a{2,3}?", "aaaa");
assertEquals(matches, 2);
}9. Захват Групп
API также позволяет нам обрабатывать несколько символов как единое целое с помощью групп захвата .
Он будет прикреплять номера к группам захвата и разрешать обратные ссылки с использованием этих номеров.
В этом разделе мы рассмотрим несколько примеров использования групп захвата в API регулярных выражений Java.
Давайте использовать группу захвата, которая совпадает только тогда, когда входной текст содержит две цифры рядом друг с другом:
@Test
public void givenCapturingGroup_whenMatches_thenCorrect() {
int maches = runTest("(\\d\\d)", "12");
assertEquals(matches, 1);
}Номер, прикрепленный к приведенному выше совпадению, является 1 , используя обратную ссылку, чтобы сообщить совпадениям, что мы хотим сопоставить другое вхождение совпадающей части текста. Таким образом, вместо:
@Test
public void givenCapturingGroup_whenMatches_thenCorrect2() {
int matches = runTest("(\\d\\d)", "1212");
assertEquals(matches, 2);
}Там, где есть два отдельных совпадения для входных данных, мы можем иметь одно совпадение, но распространять одно и то же регулярное выражение на всю длину входных данных, используя обратную ссылку:
@Test
public void givenCapturingGroup_whenMatchesWithBackReference_
thenCorrect() {
int matches = runTest("(\\d\\d)\\1", "1212");
assertEquals(matches, 1);
}Где нам пришлось бы повторить регулярное выражение без обратной ссылки, чтобы достичь того же результата:
@Test
public void givenCapturingGroup_whenMatches_thenCorrect3() {
int matches = runTest("(\\d\\d)(\\d\\d)", "1212");
assertEquals(matches, 1);
}Аналогично, для любого другого количества повторений обратные ссылки могут заставить совпадения видеть входные данные как одно совпадение:
@Test
public void givenCapturingGroup_whenMatchesWithBackReference_
thenCorrect2() {
int matches = runTest("(\\d\\d)\\1\\1\\1", "12121212");
assertEquals(matches, 1);
}Но если вы измените даже последнюю цифру, совпадение не удастся:
@Test
public void givenCapturingGroupAndWrongInput_
whenMatchFailsWithBackReference_thenCorrect() {
int matches = runTest("(\\d\\d)\\1", "1213");
assertFalse(matches > 0);
}Важно не забывать об обратных косых чертах, это очень важно в синтаксисе Java.
10. Сопоставители границ
API регулярных выражений Java также поддерживает сопоставление границ. Если мы заботимся о том, где именно во входном тексте должно произойти совпадение, то это то, что мы ищем. В предыдущих примерах все, что нас волновало, – было ли найдено совпадение или нет.
Чтобы соответствовать только тогда, когда требуемое регулярное выражение истинно в начале текста, мы используем каретку ^.
Этот тест не пройдет, так как текст dog можно найти в начале:
@Test
public void givenText_whenMatchesAtBeginning_thenCorrect() {
int matches = runTest("^dog", "dogs are friendly");
assertTrue(matches > 0);
}Следующий тест не пройдет:
@Test
public void givenTextAndWrongInput_whenMatchFailsAtBeginning_
thenCorrect() {
int matches = runTest("^dog", "are dogs are friendly?");
assertFalse(matches > 0);
}Чтобы соответствовать только тогда, когда требуемое регулярное выражение истинно в конце текста, мы используем символ доллара $. Совпадение будет найдено в следующем случае:
@Test
public void givenText_whenMatchesAtEnd_thenCorrect() {
int matches = runTest("dog$", "Man's best friend is a dog");
assertTrue(matches > 0);
}И здесь не будет найдено ни одного совпадения:
@Test
public void givenTextAndWrongInput_whenMatchFailsAtEnd_thenCorrect() {
int matches = runTest("dog$", "is a dog man's best friend?");
assertFalse(matches > 0);
}Если нам нужно совпадение только тогда, когда требуемый текст найден на границе слова, мы используем \\b регулярное выражение в начале и конце регулярного выражения:
Пространство – это граница слова:
@Test
public void givenText_whenMatchesAtWordBoundary_thenCorrect() {
int matches = runTest("\\bdog\\b", "a dog is friendly");
assertTrue(matches > 0);
}Пустая строка в начале строки также является границей слова:
@Test
public void givenText_whenMatchesAtWordBoundary_thenCorrect2() {
int matches = runTest("\\bdog\\b", "dog is man's best friend");
assertTrue(matches > 0);
}Эти тесты проходят , потому что начало строки |, а также пространство между одним текстом и другим отмечает границу слова, однако следующий тест показывает обратное:
@Test
public void givenWrongText_whenMatchFailsAtWordBoundary_thenCorrect() {
int matches = runTest("\\bdog\\b", "snoop dogg is a rapper");
assertFalse(matches > 0);
}Символы из двух слов, появляющиеся в строке, не обозначают границу слова, но мы можем сделать это, изменив конец регулярного выражения для поиска границы без слов:
@Test
public void givenText_whenMatchesAtWordAndNonBoundary_thenCorrect() {
int matches = runTest("\\bdog\\B", "snoop dogg is a rapper");
assertTrue(matches > 0);
}11. Методы класса Шаблонов
Ранее мы создавали объекты Pattern только базовым способом. Однако этот класс имеет другой вариант метода compile , который принимает набор флагов вместе с аргументом регулярного выражения, влияющим на способ сопоставления шаблона.
Эти флаги являются просто абстрагированными целочисленными значениями. Давайте перегрузим метод runTest в тестовом классе, чтобы он мог принимать флаг в качестве третьего аргумента:
public static int runTest(String regex, String text, int flags) {
pattern = Pattern.compile(regex, flags);
matcher = pattern.matcher(text);
int matches = 0;
while (matcher.find()){
matches++;
}
return matches;
}В этом разделе мы рассмотрим различные поддерживаемые флаги и то, как они используются.
В этом разделе мы рассмотрим различные поддерживаемые флаги и то, как они используются.
Этот флаг обеспечивает каноническую эквивалентность. Если указано, два символа будут считаться совпадающими, если и только если их полные канонические разложения совпадают.
Рассмотрим символ Юникода с ударением é . Его составной кодовой точкой является u00E9 . Однако в Юникоде также есть отдельная кодовая точка для составляющих его символов e , u0065 и острого акцента u0301 . В этом случае составной символ u 00E9 неотличим от последовательности из двух символов u 0065 у 0301 .
По умолчанию сопоставление не учитывает каноническую эквивалентность:
@Test
public void givenRegexWithoutCanonEq_whenMatchFailsOnEquivalentUnicode_thenCorrect() {
int matches = runTest("\u00E9", "\u0065\u0301");
assertFalse(matches > 0);
}Но если мы добавим флаг, то тест пройдет:
@Test
public void givenRegexWithCanonEq_whenMatchesOnEquivalentUnicode_thenCorrect() {
int matches = runTest("\u00E9", "\u0065\u0301", Pattern.CANON_EQ);
assertTrue(matches > 0);
}Но если мы добавим флаг, то тест пройдет:
Этот флаг позволяет сопоставлять независимо от регистра. По умолчанию сопоставление учитывает регистр:
@Test
public void givenRegexWithDefaultMatcher_whenMatchFailsOnDifferentCases_thenCorrect() {
int matches = runTest("dog", "This is a Dog");
assertFalse(matches > 0);
}Поэтому, используя этот флаг, мы можем изменить поведение по умолчанию:
@Test
public void givenRegexWithCaseInsensitiveMatcher
_whenMatchesOnDifferentCases_thenCorrect() {
int matches = runTest(
"dog", "This is a Dog", Pattern.CASE_INSENSITIVE);
assertTrue(matches > 0);
}Мы также можем использовать эквивалентное встроенное выражение флага для достижения того же результата:
@Test
public void givenRegexWithEmbeddedCaseInsensitiveMatcher
_whenMatchesOnDifferentCases_thenCorrect() {
int matches = runTest("(?i)dog", "This is a Dog");
assertTrue(matches > 0);
}Мы также можем использовать эквивалентное встроенное выражение флага для достижения того же результата:
Java API позволяет включать комментарии с использованием # в регулярное выражение. Это может помочь в документировании сложных регулярных выражений, которые могут быть не сразу очевидны другому программисту.
Флаг комментариев заставляет совпадения игнорировать любые пробелы или комментарии в регулярном выражении и учитывать только шаблон. В режиме сопоставления по умолчанию следующий тест завершится неудачей:
@Test
public void givenRegexWithComments_whenMatchFailsWithoutFlag_thenCorrect() {
int matches = runTest(
"dog$ #check for word dog at end of text", "This is a dog");
assertFalse(matches > 0);
}Это связано с тем, что совпадения будут искать все регулярное выражение во входном тексте, включая пробелы и символ#. Но когда мы используем флаг, он будет игнорировать лишние пробелы, и каждый текст, начинающийся с#, будет рассматриваться как комментарий, который будет игнорироваться для каждой строки:
@Test
public void givenRegexWithComments_whenMatchesWithFlag_thenCorrect() {
int matches = runTest(
"dog$ #check end of text","This is a dog", Pattern.COMMENTS);
assertTrue(matches > 0);
}Для этого также существует альтернативное выражение встроенного флага:
@Test
public void givenRegexWithComments_whenMatchesWithEmbeddedFlag_thenCorrect() {
int matches = runTest(
"(?x)dog$ #check end of text", "This is a dog");
assertTrue(matches > 0);
}Для этого также существует альтернативное выражение встроенного флага:
По умолчанию, когда мы используем выражение точки “.” в регулярном выражении, мы сопоставляем каждый символ во входной строке | до тех пор, пока не встретим новый символ строки.
Используя этот флаг, матч также будет включать в себя терминатор линии. Мы лучше поймем это на следующих примерах. Эти примеры будут немного отличаться. Поскольку мы заинтересованы в утверждении против совпадающей строки , мы будем использовать метод matcher ‘s group , который возвращает предыдущее совпадение.
Во-первых, мы увидим поведение по умолчанию:
@Test
public void givenRegexWithLineTerminator_whenMatchFails_thenCorrect() {
Pattern pattern = Pattern.compile("(.*)");
Matcher matcher = pattern.matcher(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line");
matcher.find();
assertEquals("this is a text", matcher.group(1));
}Как мы видим, сопоставляется только первая часть ввода перед терминатором строки.
Теперь в режиме dotall будет сопоставлен весь текст, включая терминатор строки:
@Test
public void givenRegexWithLineTerminator_whenMatchesWithDotall_thenCorrect() {
Pattern pattern = Pattern.compile("(.*)", Pattern.DOTALL);
Matcher matcher = pattern.matcher(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line");
matcher.find();
assertEquals(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line", matcher.group(1));
}Мы также можем использовать встроенное выражение флага для включения dotall mode:
@Test
public void givenRegexWithLineTerminator_whenMatchesWithEmbeddedDotall
_thenCorrect() {
Pattern pattern = Pattern.compile("(?s)(.*)");
Matcher matcher = pattern.matcher(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line");
matcher.find();
assertEquals(
"this is a text" + System.getProperty("line.separator")
+ " continued on another line", matcher.group(1));
}Мы также можем использовать встроенное выражение флага для включения || dotall || mode:
В этом режиме совпадения не придают особого значения каким-либо метасимволам, escape-символам или синтаксису регулярных выражений. Без этого флага сопоставитель будет сопоставлять следующее регулярное выражение с любыми входными данными String :
@Test
public void givenRegex_whenMatchesWithoutLiteralFlag_thenCorrect() {
int matches = runTest("(.*)", "text");
assertTrue(matches > 0);
}Это поведение по умолчанию, которое мы наблюдаем во всех примерах. Однако с этим флагом совпадение не будет найдено, так как совпадения будут искать (.*) вместо его интерпретации:
@Test
public void givenRegex_whenMatchFailsWithLiteralFlag_thenCorrect() {
int matches = runTest("(.*)", "text", Pattern.LITERAL);
assertFalse(matches > 0);
}Теперь, если мы добавим необходимую строку, тест пройдет:
@Test
public void givenRegex_whenMatchesWithLiteralFlag_thenCorrect() {
int matches = runTest("(.*)", "text(.*)", Pattern.LITERAL);
assertTrue(matches > 0);
}Нет встроенного символа флага для включения разбора литералов.
Нет встроенного символа флага для включения разбора литералов.
По умолчанию ^ и $ метасимволы полностью совпадают в начале и в конце соответственно всей входной строки . Сопоставитель игнорирует любые терминаторы строк:
@Test
public void givenRegex_whenMatchFailsWithoutMultilineFlag_thenCorrect() {
int matches = runTest(
"dog$", "This is a dog" + System.getProperty("line.separator")
+ "this is a fox");
assertFalse(matches > 0);
}Совпадение не удается, потому что сопоставитель ищет dog в конце всей строки , но dog присутствует в конце первой строки строки.
Однако с флагом тот же тест будет пройден, так как теперь в матчах учитываются терминаторы строк. Таким образом, строка dog найдена непосредственно перед завершением строки, следовательно, успех:
@Test
public void givenRegex_whenMatchesWithMultilineFlag_thenCorrect() {
int matches = runTest(
"dog$", "This is a dog" + System.getProperty("line.separator")
+ "this is a fox", Pattern.MULTILINE);
assertTrue(matches > 0);
}Вот версия встроенного флага:
@Test
public void givenRegex_whenMatchesWithEmbeddedMultilineFlag_
thenCorrect() {
int matches = runTest(
"(?m)dog$", "This is a dog" + System.getProperty("line.separator")
+ "this is a fox");
assertTrue(matches > 0);
}12. Соответствует методам класса
В этом разделе мы рассмотрим некоторые полезные методы класса Matcher . Мы сгруппируем их в соответствии с функциональными возможностями для ясности.
12.1. Методы индексации
Методы индекса предоставляют полезные значения индекса, которые точно показывают, где было найдено совпадение во входной строке |. В следующем тесте мы подтвердим начальный и конечный индексы совпадения для dog во входной строке :
@Test
public void givenMatch_whenGetsIndices_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher("This dog is mine");
matcher.find();
assertEquals(5, matcher.start());
assertEquals(8, matcher.end());
}12.2. Методы исследования
Методы исследования проходят через вход String и возвращают логическое значение, указывающее, найден ли шаблон или нет. Обычно используются методы matches и lookingAt .
Методы matches и lookingAt пытаются сопоставить входную последовательность с шаблоном. Разница в том, что соответствует требует, чтобы вся входная последовательность была согласована, в то время как смотрит на нет.
Оба метода начинаются с начала ввода String :
@Test
public void whenStudyMethodsWork_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher("dogs are friendly");
assertTrue(matcher.lookingAt());
assertFalse(matcher.matches());
}Метод matches вернет true в таком случае:
@Test
public void whenMatchesStudyMethodWorks_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher("dog");
assertTrue(matcher.matches());
}12.3. Методы замены
Методы замены полезны для замены текста во входной строке. Наиболее распространенными являются replaceFirst и replaceAll .
Методы replaceFirst и replaceAll заменяют текст, соответствующий заданному регулярному выражению. Как показывают их имена, replaceFirst заменяет первое вхождение, а replaceAll заменяет все вхождения:
@Test
public void whenReplaceFirstWorks_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher(
"dogs are domestic animals, dogs are friendly");
String newStr = matcher.replaceFirst("cat");
assertEquals(
"cats are domestic animals, dogs are friendly", newStr);
}Замените все вхождения:
@Test
public void whenReplaceAllWorks_thenCorrect() {
Pattern pattern = Pattern.compile("dog");
Matcher matcher = pattern.matcher(
"dogs are domestic animals, dogs are friendly");
String newStr = matcher.replaceAll("cat");
assertEquals("cats are domestic animals, cats are friendly", newStr);
}Метод replaceAll позволяет нам заменить все совпадения одной и той же заменой. Если мы хотим заменить совпадения в каждом конкретном случае, нам понадобится метод замены токенов .
13. Заключение
В этой статье мы узнали, как использовать регулярные выражения в Java, а также изучили наиболее важные функции пакета java.util.regex .
Полный исходный код проекта, включая все примеры кода, используемые здесь, можно найти в проекте GitHub .