Hướng dẫn về API cụm từ thông dụng Java

1. Khái quát chung

Trong bài viết này, chúng ta sẽ thảo luận về Java Regex API và cách các biểu thức chính quy có thể được sử dụng trong ngôn ngữ lập trình Java.

Trong thế giới của biểu thức chính quy, có rất nhiều hương vị khác nhau để lựa chọn, chẳng hạn như grep, Perl, Python, PHP, awk và nhiều hơn nữa.

Điều này có nghĩa là một biểu thức chính quy hoạt động trong một ngôn ngữ lập trình có thể không hoạt động trong một ngôn ngữ lập trình khác. Cú pháp biểu thức chính quy trong Java gần giống nhất với cú pháp trong Perl.

2. Thiết lập

Để sử dụng biểu thức chính quy trong Java, chúng ta không cần bất kỳ thiết lập đặc biệt nào. JDK chứa một gói đặc biệt java.util.regex hoàn toàn dành riêng cho các hoạt động regex. Chúng tôi chỉ cần nhập nó vào mã của chúng tôi.

Hơn nữa, lớp java.lang.String cũng có hỗ trợ regex dựng sẵn mà chúng tôi thường sử dụng trong mã của mình.

3. Gói Java Regex

Các java.util.regex gói bao gồm ba lớp: Pattern, MatcherPatternSyntaxException:

  • Đối tượng Pattern là một regex đã được biên dịch. Lớp Pattern không cung cấp hàm tạo công khai. Để tạo một mẫu, trước tiên chúng ta phải gọi một trong các phương thức biên dịch tĩnh công khai của nó , sau đó sẽ trả về một đối tượng Mẫu . Các phương thức này chấp nhận một biểu thức chính quy làm đối số đầu tiên.
  • Đối tượng Matcher diễn giải mẫu và thực hiện các hoạt động so khớp với một Chuỗi đầu vào . Nó cũng xác định không có hàm tạo công khai. Chúng ta có được một đối tượng Matcher bằng cách gọi phương thức so khớp trên một đối tượng Pattern .
  • Đối tượng PatternSyntaxException là một ngoại lệ không được kiểm tra chỉ ra lỗi cú pháp trong một mẫu biểu thức chính quy.

Chúng ta sẽ tìm hiểu chi tiết các lớp này; tuy nhiên, trước tiên chúng ta phải hiểu cách regex được xây dựng trong Java.

Nếu bạn đã quen với regex từ một môi trường khác, bạn có thể tìm thấy những khác biệt nhất định, nhưng chúng là rất ít.

4. Ví dụ đơn giản

Hãy bắt đầu với trường hợp sử dụng đơn giản nhất cho regex. Như chúng ta đã lưu ý trước đó, khi một regex được áp dụng cho một Chuỗi, nó có thể khớp với 0 hoặc nhiều lần.

Dạng đối sánh mẫu cơ bản nhất được hỗ trợ bởi API java.util.regexđối sánh của một chuỗi ký tự . Ví dụ: nếu biểu thức chính quy là fooChuỗi đầu vào là foo , kết quả khớp sẽ thành công vì các Chuỗi giống hệt nhau:

@Test public void givenText_whenSimpleRegexMatches_thenCorrect() { Pattern pattern = Pattern.compile("foo"); Matcher matcher = pattern.matcher("foo"); assertTrue(matcher.find()); }

Đầu tiên chúng ta tạo một đối tượng Pattern bằng cách gọi phương thức biên dịch tĩnh của nó và chuyển cho nó một mẫu mà chúng ta muốn sử dụng.

Sau đó, chúng tôi tạo ra một Matcher đối tượng được gọi Pattern đối tượng của khớp phương pháp và đi qua nó các văn bản chúng ta muốn kiểm tra cho phù hợp.

Sau đó, chúng ta gọi phương thức find trong đối tượng Matcher.

Các tìm phương pháp giữ tiến thông qua các văn bản đầu vào và trả về đúng cho mỗi trận đấu, vì vậy chúng ta có thể sử dụng nó để tìm ra số trận đấu cũng như:

@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); }

Vì chúng tôi sẽ chạy nhiều thử nghiệm hơn, chúng tôi có thể tóm tắt logic để tìm số lượng kết quả phù hợp trong một phương thức được gọi là runTest :

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; }

Khi chúng tôi nhận được 0 trận đấu, bài kiểm tra sẽ không thành công, nếu không, nó sẽ vượt qua.

5. Nhân vật Meta

Các ký tự meta ảnh hưởng đến cách một mẫu được đối sánh, theo cách thêm logic vào mẫu tìm kiếm. API Java hỗ trợ một số siêu ký tự, đơn giản nhất là dấu chấm “.” phù hợp với bất kỳ ký tự nào:

@Test public void givenText_whenMatchesWithDotMetach_thenCorrect() { int matches = runTest(".", "foo"); assertTrue(matches > 0); }

Xem xét ví dụ trước trong đó regex foo khớp văn bản foo cũng như foofoo hai lần. Nếu chúng tôi sử dụng siêu ký tự dấu chấm trong regex, chúng tôi sẽ không nhận được hai kết quả phù hợp trong trường hợp thứ hai:

@Test public void givenRepeatedText_whenMatchesOnceWithDotMetach_thenCorrect() { int matches= runTest("foo.", "foofoo"); assertEquals(matches, 1); }

Chú ý dấu chấm sau foo trong regex. Trình so khớp khớp với mọi văn bản đứng trước foo vì phần dấu chấm cuối cùng có nghĩa là bất kỳ ký tự nào đứng sau. Vì vậy, sau khi tìm thấy foo đầu tiên , phần còn lại được xem như bất kỳ ký tự nào. Đó là lý do tại sao chỉ có một trận đấu duy nhất.

API hỗ trợ một số ký tự meta khác mà chúng tôi sẽ xem xét thêm trong bài viết này.

6. Các lớp nhân vật

Duyệt qua thông số kỹ thuật chính thức của lớp Pattern , chúng tôi sẽ khám phá các bản tóm tắt về các cấu trúc regex được hỗ trợ. Dưới các lớp nhân vật, chúng ta có khoảng 6 cấu trúc.

6.1. HOẶC Lớp

Được tạo thành [abc] . Bất kỳ phần tử nào trong tập hợp được khớp:

@Test public void givenORSet_whenMatchesAny_thenCorrect() { int matches = runTest("[abc]", "b"); assertEquals(matches, 1); }

Nếu tất cả chúng đều xuất hiện trong văn bản, mỗi cái được khớp riêng biệt không liên quan đến thứ tự:

@Test public void givenORSet_whenMatchesAnyAndAll_thenCorrect() { int matches = runTest("[abc]", "cab"); assertEquals(matches, 3); }

Chúng cũng có thể được xen kẽ như một phần của Chuỗi . Trong ví dụ sau, khi chúng ta tạo các từ khác nhau bằng cách xen kẽ chữ cái đầu tiên với mỗi phần tử của tập hợp, tất cả chúng đều khớp:

@Test public void givenORSet_whenMatchesAllCombinations_thenCorrect() { int matches = runTest("[bcr]at", "bat cat rat"); assertEquals(matches, 3); }

6.2. Lớp NOR

Tập hợp trên bị phủ định bằng cách thêm dấu mũ làm phần tử đầu tiên:

@Test public void givenNORSet_whenMatchesNon_thenCorrect() { int matches = runTest("[^abc]", "g"); assertTrue(matches > 0); }

Một trường hợp khác:

@Test public void givenNORSet_whenMatchesAllExceptElements_thenCorrect() { int matches = runTest("[^bcr]at", "sat mat eat"); assertTrue(matches > 0); }

6.3. Lớp phạm vi

Chúng ta có thể xác định một lớp chỉ định một phạm vi trong đó văn bản phù hợp sẽ nằm trong dấu gạch ngang (-), tương tự, chúng ta cũng có thể phủ định một phạm vi.

Khớp các chữ hoa:

@Test public void givenUpperCaseRange_whenMatchesUpperCase_ thenCorrect() { int matches = runTest( "[A-Z]", "Two Uppercase alphabets 34 overall"); assertEquals(matches, 2); }

Khớp các chữ thường:

@Test public void givenLowerCaseRange_whenMatchesLowerCase_ thenCorrect() { int matches = runTest( "[a-z]", "Two Uppercase alphabets 34 overall"); assertEquals(matches, 26); }

Khớp cả chữ hoa và chữ thường:

@Test public void givenBothLowerAndUpperCaseRange_ whenMatchesAllLetters_thenCorrect() { int matches = runTest( "[a-zA-Z]", "Two Uppercase alphabets 34 overall"); assertEquals(matches, 28); }

Đối sánh với một dải số nhất định:

@Test public void givenNumberRange_whenMatchesAccurately_ thenCorrect() { int matches = runTest( "[1-5]", "Two Uppercase alphabets 34 overall"); assertEquals(matches, 2); }

Khớp với một dải số khác:

@Test public void givenNumberRange_whenMatchesAccurately_ thenCorrect2(){ int matches = runTest( "[30-35]", "Two Uppercase alphabets 34 overall"); assertEquals(matches, 1); }

6.4. Lớp công đoàn

Lớp ký tự liên hợp là kết quả của việc kết hợp hai hoặc nhiều lớp ký tự:

@Test public void givenTwoSets_whenMatchesUnion_thenCorrect() { int matches = runTest("[1-3[7-9]]", "123456789"); assertEquals(matches, 6); }

Kiểm tra trên sẽ chỉ khớp với 6 trong số 9 số nguyên vì tập hợp bỏ qua 4, 5 và 6.

6.5. Lớp giao nhau

Tương tự như lớp union, lớp này là kết quả từ việc chọn các phần tử chung giữa hai hoặc nhiều tập hợp. Để áp dụng giao lộ, chúng tôi sử dụng && :

@Test public void givenTwoSets_whenMatchesIntersection_thenCorrect() { int matches = runTest("[1-6&&[3-9]]", "123456789"); assertEquals(matches, 4); }

We get 4 matches because the intersection of the two sets has only 4 elements.

6.6. Subtraction Class

We can use subtraction to negate one or more character classes, for example matching a set of odd decimal numbers:

@Test public void givenSetWithSubtraction_whenMatchesAccurately_thenCorrect() { int matches = runTest("[0-9&&[^2468]]", "123456789"); assertEquals(matches, 5); }

Only 1,3,5,7,9 will be matched.

7. Predefined Character Classes

The Java regex API also accepts predefined character classes. Some of the above character classes can be expressed in shorter form though making the code less intuitive. One special aspect of the Java version of this regex is the escape character.

As we will see, most characters will start with a backslash, which has a special meaning in Java. For these to be compiled by the Pattern class – the leading backslash must be escaped i.e. \d becomes \\d.

Matching digits, equivalent to [0-9]:

@Test public void givenDigits_whenMatches_thenCorrect() { int matches = runTest("\\d", "123"); assertEquals(matches, 3); }

Matching non-digits, equivalent to [^0-9]:

@Test public void givenNonDigits_whenMatches_thenCorrect() { int mathces = runTest("\\D", "a6c"); assertEquals(matches, 2); }

Matching white space:

@Test public void givenWhiteSpace_whenMatches_thenCorrect() { int matches = runTest("\\s", "a c"); assertEquals(matches, 1); }

Matching non-white space:

@Test public void givenNonWhiteSpace_whenMatches_thenCorrect() { int matches = runTest("\\S", "a c"); assertEquals(matches, 2); }

Matching a word character, equivalent to [a-zA-Z_0-9]:

@Test public void givenWordCharacter_whenMatches_thenCorrect() { int matches = runTest("\\w", "hi!"); assertEquals(matches, 2); }

Matching a non-word character:

@Test public void givenNonWordCharacter_whenMatches_thenCorrect() { int matches = runTest("\\W", "hi!"); assertEquals(matches, 1); }

8. Quantifiers

The Java regex API also allows us to use quantifiers. These enable us to further tweak the match's behavior by specifying the number of occurrences to match against.

To match a text zero or one time, we use the ? quantifier:

@Test public void givenZeroOrOneQuantifier_whenMatches_thenCorrect() { int matches = runTest("\\a?", "hi"); assertEquals(matches, 3); }

Alternatively, we can use the brace syntax, also supported by the Java regex API:

@Test public void givenZeroOrOneQuantifier_whenMatches_thenCorrect2() { int matches = runTest("\\a{0,1}", "hi"); assertEquals(matches, 3); }

This example introduces the concept of zero-length matches. It so happens that if a quantifier's threshold for matching is zero, it always matches everything in the text including an empty String at the end of every input. This means that even if the input is empty, it will return one zero-length match.

This explains why we get 3 matches in the above example despite having a String of length two. The third match is zero-length empty String.

To match a text zero or limitless times, we us * quantifier, it is just similar to ?:

@Test public void givenZeroOrManyQuantifier_whenMatches_thenCorrect() { int matches = runTest("\\a*", "hi"); assertEquals(matches, 3); }

Supported alternative:

@Test public void givenZeroOrManyQuantifier_whenMatches_thenCorrect2() { int matches = runTest("\\a{0,}", "hi"); assertEquals(matches, 3); }

The quantifier with a difference is +, it has a matching threshold of 1. If the required String does not occur at all, there will be no match, not even a zero-length String:

@Test public void givenOneOrManyQuantifier_whenMatches_thenCorrect() { int matches = runTest("\\a+", "hi"); assertFalse(matches); }

Supported alternative:

@Test public void givenOneOrManyQuantifier_whenMatches_thenCorrect2() { int matches = runTest("\\a{1,}", "hi"); assertFalse(matches); }

As it is in Perl and other languages, the brace syntax can be used to match a given text a number of times:

@Test public void givenBraceQuantifier_whenMatches_thenCorrect() { int matches = runTest("a{3}", "aaaaaa"); assertEquals(matches, 2); }

In the above example, we get two matches since a match occurs only if a appears three times in a row. However, in the next test we won't get a match since the text only appears two times in a row:

@Test public void givenBraceQuantifier_whenFailsToMatch_thenCorrect() { int matches = runTest("a{3}", "aa"); assertFalse(matches > 0); }

When we use a range in the brace, the match will be greedy, matching from the higher end of the range:

@Test public void givenBraceQuantifierWithRange_whenMatches_thenCorrect() { int matches = runTest("a{2,3}", "aaaa"); assertEquals(matches, 1); }

We've specified at least two occurrences but not exceeding three, so we get a single match instead where the matcher sees a single aaa and a lone a which can't be matched.

However, the API allows us to specify a lazy or reluctant approach such that the matcher can start from the lower end of the range in which case matching two occurrences as aa and aa:

@Test public void givenBraceQuantifierWithRange_whenMatchesLazily_thenCorrect() { int matches = runTest("a{2,3}?", "aaaa"); assertEquals(matches, 2); }

9. Capturing Groups

The API also allows us to treat multiple characters as a single unit through capturing groups.

It will attache numbers to the capturing groups and allow back referencing using these numbers.

In this section, we will see a few examples on how to use capturing groups in Java regex API.

Let's use a capturing group that matches only when an input text contains two digits next to each other:

@Test public void givenCapturingGroup_whenMatches_thenCorrect() { int maches = runTest("(\\d\\d)", "12"); assertEquals(matches, 1); }

The number attached to the above match is 1, using a back reference to tell the matcher that we want to match another occurrence of the matched portion of the text. This way, instead of:

@Test public void givenCapturingGroup_whenMatches_thenCorrect2() { int matches = runTest("(\\d\\d)", "1212"); assertEquals(matches, 2); }

Where there are two separate matches for the input, we can have one match but propagating the same regex match to span the entire length of the input using back referencing:

@Test public void givenCapturingGroup_whenMatchesWithBackReference_ thenCorrect() { int matches = runTest("(\\d\\d)\\1", "1212"); assertEquals(matches, 1); }

Where we would have to repeat the regex without back referencing to achieve the same result:

@Test public void givenCapturingGroup_whenMatches_thenCorrect3() { int matches = runTest("(\\d\\d)(\\d\\d)", "1212"); assertEquals(matches, 1); }

Similarly, for any other number of repetitions, back referencing can make the matcher see the input as a single match:

@Test public void givenCapturingGroup_whenMatchesWithBackReference_ thenCorrect2() { int matches = runTest("(\\d\\d)\\1\\1\\1", "12121212"); assertEquals(matches, 1); }

But if you change even the last digit, the match will fail:

@Test public void givenCapturingGroupAndWrongInput_ whenMatchFailsWithBackReference_thenCorrect() { int matches = runTest("(\\d\\d)\\1", "1213"); assertFalse(matches > 0); }

It is important not to forget the escape backslashes, this is crucial in Java syntax.

10. Boundary Matchers

The Java regex API also supports boundary matching. If we care about where exactly in the input text the match should occur, then this is what we are looking for. With the previous examples, all we cared about was whether a match was found or not.

To match only when the required regex is true at the beginning of the text, we use the caret ^.

This test will fail since the text dog can be found at the beginning:

@Test public void givenText_whenMatchesAtBeginning_thenCorrect() { int matches = runTest("^dog", "dogs are friendly"); assertTrue(matches > 0); }

The following test will fail:

@Test public void givenTextAndWrongInput_whenMatchFailsAtBeginning_ thenCorrect() { int matches = runTest("^dog", "are dogs are friendly?"); assertFalse(matches > 0); }

To match only when the required regex is true at the end of the text, we use the dollar character $. A match will be found in the following case:

@Test public void givenText_whenMatchesAtEnd_thenCorrect() { int matches = runTest("dog$", "Man's best friend is a dog"); assertTrue(matches > 0); }

And no match will be found here:

@Test public void givenTextAndWrongInput_whenMatchFailsAtEnd_thenCorrect() { int matches = runTest("dog$", "is a dog man's best friend?"); assertFalse(matches > 0); }

If we want a match only when the required text is found at a word boundary, we use \\b regex at the beginning and end of the regex:

Space is a word boundary:

@Test public void givenText_whenMatchesAtWordBoundary_thenCorrect() { int matches = runTest("\\bdog\\b", "a dog is friendly"); assertTrue(matches > 0); }

The empty string at the beginning of a line is also a word boundary:

@Test public void givenText_whenMatchesAtWordBoundary_thenCorrect2() { int matches = runTest("\\bdog\\b", "dog is man's best friend"); assertTrue(matches > 0); }

These tests pass because the beginning of a String, as well as space between one text and another, marks a word boundary, however, the following test shows the opposite:

@Test public void givenWrongText_whenMatchFailsAtWordBoundary_thenCorrect() { int matches = runTest("\\bdog\\b", "snoop dogg is a rapper"); assertFalse(matches > 0); }

Two-word characters appearing in a row does not mark a word boundary, but we can make it pass by changing the end of the regex to look for a non-word boundary:

@Test public void givenText_whenMatchesAtWordAndNonBoundary_thenCorrect() { int matches = runTest("\\bdog\\B", "snoop dogg is a rapper"); assertTrue(matches > 0); }

11. Pattern Class Methods

Previously, we have only created Pattern objects in a basic way. However, this class has another variant of the compile method that accepts a set of flags alongside the regex argument affecting the way the pattern is matched.

These flags are simply abstracted integer values. Let's overload the runTest method in the test class so that it can take a flag as the third argument:

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; }

In this section, we will look at the different supported flags and how they are used.

Pattern.CANON_EQ

This flag enables canonical equivalence. When specified, two characters will be considered to match if, and only if, their full canonical decompositions match.

Consider the accented Unicode character é. Its composite code point is u00E9. However, Unicode also has a separate code point for its component characters e, u0065 and the acute accent, u0301. In this case, composite character u00E9 is indistinguishable from the two character sequence u0065 u0301.

By default, matching does not take canonical equivalence into account:

@Test public void givenRegexWithoutCanonEq_whenMatchFailsOnEquivalentUnicode_thenCorrect() { int matches = runTest("\u00E9", "\u0065\u0301"); assertFalse(matches > 0); }

But if we add the flag, then the test will pass:

@Test public void givenRegexWithCanonEq_whenMatchesOnEquivalentUnicode_thenCorrect() { int matches = runTest("\u00E9", "\u0065\u0301", Pattern.CANON_EQ); assertTrue(matches > 0); }

Pattern.CASE_INSENSITIVE

This flag enables matching regardless of case. By default matching takes case into account:

@Test public void givenRegexWithDefaultMatcher_whenMatchFailsOnDifferentCases_thenCorrect() { int matches = runTest("dog", "This is a Dog"); assertFalse(matches > 0); }

So using this flag, we can change the default behavior:

@Test public void givenRegexWithCaseInsensitiveMatcher _whenMatchesOnDifferentCases_thenCorrect() { int matches = runTest( "dog", "This is a Dog", Pattern.CASE_INSENSITIVE); assertTrue(matches > 0); }

We can also use the equivalent, embedded flag expression to achieve the same result:

@Test public void givenRegexWithEmbeddedCaseInsensitiveMatcher _whenMatchesOnDifferentCases_thenCorrect() { int matches = runTest("(?i)dog", "This is a Dog"); assertTrue(matches > 0); }

Pattern.COMMENTS

The Java API allows one to include comments using # in the regex. This can help in documenting complex regex that may not be immediately obvious to another programmer.

The comments flag makes the matcher ignore any white space or comments in the regex and only consider the pattern. In the default matching mode the following test would fail:

@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); }

This is because the matcher will look for the entire regex in the input text, including the spaces and the # character. But when we use the flag, it will ignore the extra spaces and the every text starting with # will be seen as a comment to be ignored for each line:

@Test public void givenRegexWithComments_whenMatchesWithFlag_thenCorrect() { int matches = runTest( "dog$ #check end of text","This is a dog", Pattern.COMMENTS); assertTrue(matches > 0); }

There is also an alternative embedded flag expression for this:

@Test public void givenRegexWithComments_whenMatchesWithEmbeddedFlag_thenCorrect() { int matches = runTest( "(?x)dog$ #check end of text", "This is a dog"); assertTrue(matches > 0); }

Pattern.DOTALL

By default, when we use the dot “.” expression in regex, we are matching every character in the input String until we encounter a new line character.

Using this flag, the match will include the line terminator as well. We will understand better with the following examples. These examples will be a little different. Since we are interested in asserting against the matched String, we will use matcher‘s group method which returns the previous match.

First, we will see the default behavior:

@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)); }

As we can see, only the first part of the input before the line terminator is matched.

Now in dotall mode, the entire text including the line terminator will be matched:

@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)); }

We can also use an embedded flag expression to enable 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)); }

Pattern.LITERAL

When in this mode, matcher gives no special meaning to any metacharacters, escape characters or regex syntax. Without this flag, the matcher will match the following regex against any input String:

@Test public void givenRegex_whenMatchesWithoutLiteralFlag_thenCorrect() { int matches = runTest("(.*)", "text"); assertTrue(matches > 0); }

This is the default behavior we have been seeing in all the examples. However, with this flag, no match will be found, since the matcher will be looking for (.*) instead of interpreting it:

@Test public void givenRegex_whenMatchFailsWithLiteralFlag_thenCorrect() { int matches = runTest("(.*)", "text", Pattern.LITERAL); assertFalse(matches > 0); }

Now if we add the required string, the test will pass:

@Test public void givenRegex_whenMatchesWithLiteralFlag_thenCorrect() { int matches = runTest("(.*)", "text(.*)", Pattern.LITERAL); assertTrue(matches > 0); }

There is no embedded flag character for enabling literal parsing.

Pattern.MULTILINE

By default ^ and $ metacharacters match absolutely at the beginning and at the end respectively of the entire input String. The matcher disregards any line terminators:

@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); }

The match fails because the matcher searches for dog at the end of the entire String but the dog is present at the end of the first line of the string.

However, with the flag, the same test will pass since the matcher now takes into account line terminators. So the String dog is found just before the line terminates, hence success:

@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); }

Here is the embedded flag version:

@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 Class Methods

In this section, we will look at some useful methods of the Matcher class. We will group them according to functionality for clarity.

12.1. Index Methods

Index methods provide useful index values that show precisely where the match was found in the input String . In the following test, we will confirm the start and end indices of the match for dog in the input String :

@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. Study Methods

Study methods go through the input String and return a boolean indicating whether or not the pattern is found. Commonly used are matches and lookingAt methods.

The matches and lookingAt methods both attempt to match an input sequence against a pattern. The difference, is that matches requires the entire input sequence to be matched, while lookingAt does not.

Both methods start at the beginning of the input String :

@Test public void whenStudyMethodsWork_thenCorrect() { Pattern pattern = Pattern.compile("dog"); Matcher matcher = pattern.matcher("dogs are friendly"); assertTrue(matcher.lookingAt()); assertFalse(matcher.matches()); }

The matches method will return true in a case like so:

@Test public void whenMatchesStudyMethodWorks_thenCorrect() { Pattern pattern = Pattern.compile("dog"); Matcher matcher = pattern.matcher("dog"); assertTrue(matcher.matches()); }

12.3. Replacement Methods

Replacement methods are useful to replace text in an input string. The common ones are replaceFirst and replaceAll.

Các replaceFirstreplaceAll phương pháp thay thế văn bản phù hợp với một biểu thức chính quy định. Như tên của chúng chỉ ra, ReplaceFirst thay thế lần xuất hiện đầu tiên và ReplaceAll thay thế tất cả các lần xuất hiện:

@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); }

Thay thế tất cả các lần xuất hiện:

@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); }

Các replaceAll phương pháp cho phép chúng ta thay thế tất cả các trận đấu với sự thay thế tương tự. Nếu chúng tôi muốn thay thế các trận đấu theo từng trường hợp, chúng tôi sẽ cần một kỹ thuật thay thế mã thông báo.

13. Kết luận

Trong bài này, chúng ta đã học cách sử dụng các biểu thức chính quy trong Java và cũng khám phá các tính năng quan trọng nhất của gói java.util.regex .

Bạn có thể tìm thấy mã nguồn đầy đủ cho dự án bao gồm tất cả các mẫu mã được sử dụng ở đây trong dự án GitHub.