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 .