التعبيرات المنتظمة في Java: دليل مفصل
What is it and why? (a real-life analogy)
Imagine you are a librarian in a huge library containing millions of books. A reader comes to you and says: "I need all books where the cover has the word 'Adventures', and on page 25 there is a mention of 'gold', and the author was born no earlier than 1980." If you had to look through each book manually, it would take years. But if you had a magic scanner that you could simply tell: "Find everything that matches this pattern," the job would be done in seconds.
Regular Expressions (or regex for short) are exactly that kind of "magic scanner" for text. It is a special pattern language that allows you to:
- Find fragments in text based on complex conditions
- Check if a string matches a specific format (e.g., email or phone number)
- Replace one part of text with another
- Split text into parts based on specified delimiters
💡 Simple analogy: A regular expression is like a mask for searching files in a file explorer. If you type *.txt, the computer will find all text files. Regular expressions are the same idea, but hundreds of times more powerful and flexible.
In Java, regular expressions have been available since version 1.4 (2002) and have since become an indispensable tool for any developer. They are used everywhere: from password validation in applications to parsing complex server logs.
Theory: how it works internally
To understand how regular expressions work, you need to grasp two key concepts: pattern and finite automaton.
Pattern — a search program
When you write a regular expression, you are essentially creating a mini-program. This program says: "Go through the text character by character and check if the current position matches my rules." Java takes your pattern and compiles it into a special internal data structure — a finite automaton.
Finite automaton — the heart of regex
Imagine a robot standing on rails (the text string). The robot has several states:
- State 0: "I haven't found anything yet, looking for the start of a match"
- State 1: "I found the first letter 'c', checking further"
- State 2: "I found 'ca', continuing"
- State 3 (final): "I found 'cat'! Recording the result and looking for the next one"
The robot moves through the text, switching states depending on which characters it encounters. If it reaches a final state — a match is found. If it encounters a character that doesn't fit any rule — it goes back and tries another path. This process is called backtracking.
⚠️ Important: The backtracking mechanism is the most resource-intensive part of regular expressions. A poorly written pattern can force the computer to try millions of combinations, causing the program to freeze. This is called "catastrophic backtracking".
Regular expression engine in Java
Java uses an NFA (Nondeterministic Finite Automaton). This means that at the same position, there can be several possible continuations, and the engine tries them one by one. The alternative is DFA (Deterministic Finite Automaton), which is faster but less flexible. Java chose NFA because it supports more features: backreferences, lookahead, and lookbehind.
Basic components of any regex
| Component | Description | Example |
|---|---|---|
| Literals | Ordinary characters that match themselves | cat finds the string "cat" |
| Metacharacters | Characters with special meaning | . — any character, \d — digit |
| Quantifiers | Specify how many times a character repeats | * — 0 or more, + — 1 or more |
| Groups | Combine part of the pattern into a single unit | (ab)+ — "ab", "abab", "ababab" |
| Anchors | Bind the search to the beginning/end of the string | ^ — start, $ — end |
Syntax in Java: all forms and options
In Java, working with regular expressions is centered around two classes: java.util.regex.Pattern and java.util.regex.Matcher. There are also convenient methods in the String class.
Pattern class — pattern factory
This class cannot be created using new. Instead, the static method compile() is used:
// Compile the pattern
Pattern pattern = Pattern.compile("\\d+"); // Pattern: one or more digits
// You can add flags (options)
Pattern patternWithFlags = Pattern.compile("[a-z]+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher class — the executor
A Matcher is created from a Pattern and applied to a specific string:
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("Year 2024, month 12");
// Main Matcher methods:
// find() — searches for the next match (returns boolean)
// matches() — checks if the ENTIRE string matches the pattern
// group() — returns the found match
// start() / end() — positions of the start and end of the match
while (matcher.find()) {
System.out.println("Found: " + matcher.group() + " at position " + matcher.start());
}
// Output:
// Found: 2024 at position 5
// Found: 12 at position 18
Convenient methods in the String class
Java's String class provides several methods that internally use regex:
String text = "Hello, world! Hello, Java!";
// matches() — checks if the entire string matches the pattern
boolean isMatch = text.matches("Hello.*"); // true
// replaceAll() — replaces all matches
String replaced = text.replaceAll("Hello", "Hi"); // "Hi, world! Hi, Java!"
// split() — splits the string by a pattern
String[] parts = text.split(",\\s*"); // ["Hello", "world! Hello", "Java!"]
Practical examples with detailed explanations
Example 1: Email validation
One of the most common tasks. Let's create a pattern for a simple email:
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
String email = "user@example.com";
boolean isValid = email.matches(emailRegex);
System.out.println("Is email valid? " + isValid); // true
// Explanation of the pattern:
// ^ — start of string
// [A-Za-z0-9+_.-]+ — one or more allowed characters (letters, digits, +, _, ., -)
// @ — literal @ symbol
// [A-Za-z0-9.-]+ — one or more characters for the domain name
// \. — literal dot (escaped)
// [A-Za-z]{2,} — top-level domain (at least 2 letters)
// $ — end of string
Example 2: Extracting phone numbers from text
Let's find all phone numbers in the format (XXX) XXX-XXXX:
String text = "Call me at (123) 456-7890 or (987) 654-3210.";
Pattern pattern = Pattern.compile("\\(\\d{3}\\) \\d{3}-\\d{4}");
Matcher matcher = pattern.matcher(text);
System.out.println("Found phone numbers:");
while (matcher.find()) {
System.out.println(matcher.group());
}
// Output:
// Found phone numbers:
// (123) 456-7890
// (987) 654-3210
Example 3: Replacing dates
Let's replace dates from the format DD.MM.YYYY to YYYY-MM-DD:
String text = "Today is 15.03.2024. Tomorrow is 16.03.2024.";
String result = text.replaceAll("(\\d{2})\\.(\\d{2})\\.(\\d{4})", "$3-$2-$1");
System.out.println(result);
// Output: Today is 2024-03-15. Tomorrow is 2024-03-16.
// Explanation:
// (\\d{2}) — group 1: day (2 digits)
// \. — literal dot
// (\\d{2}) — group 2: month (2 digits)
// \. — literal dot
// (\\d{4}) — group 3: year (4 digits)
// $3-$2-$1 — replacement: year-month-day
Common mistakes and how to avoid them
Mistake 1: Forgetting to escape special characters
In Java strings, the backslash \ is an escape character. To write a literal backslash in a regex, you need to double it: \\.
// Wrong: Pattern pattern = Pattern.compile("\d"); // compilation error
// Correct:
Pattern correct = Pattern.compile("\\d"); // matches a digit
Mistake 2: Greedy quantifiers causing catastrophic backtracking
Greedy quantifiers (*, +) try to capture as much text as possible. If the pattern is complex, this can lead to performance issues.
// Dangerous pattern (can cause backtracking on long strings):
String dangerous = "(a+)+b";
// Better to use atomic groups or possessive quantifiers:
String safe = "(?>a+)b"; // atomic group
// Or use a more specific pattern
Mistake 3: Using matches() when you need find()
matches() tries to match the entire string against the pattern. If you just want to find a substring, use find().
String text = "The number 42 is important.";
Pattern pattern = Pattern.compile("\\d+");
// Wrong: matcher.matches() will return false because the whole string is not just digits
Matcher matcher = pattern.matcher(text);
System.out.println(matcher.matches()); // false
// Correct: use find()
matcher.find();
System.out.println(matcher.group()); // 42
Conclusion and recommendations
Regular expressions are a powerful tool, but they require careful handling. Here are a few tips:
- Start simple: Don't try to write a complex pattern right away. Build it step by step.
- Test your patterns: Use online regex testers or write small Java programs to verify.
- Be careful with backtracking: If your pattern works slowly, try to simplify it or use atomic groups.
- Use comments in complex patterns: Java allows you to add comments to patterns using the
(?#comment)syntax or thePattern.COMMENTSflag. - Remember about escaping: In Java strings, always double the backslash for regex metacharacters.
💡 Final thought: A regular expression is like a Swiss Army knife for text processing. It can solve many problems elegantly, but sometimes a simple```String.contains()orString.startsWith()is enough. Don't overcomplicate things!