В чому особливість методу replace() vs replaceAll()?
Обидва методи замінюють всі знайдені входження. Різниця в тому, як вони шукають:
🟢 Junior Level
Обидва методи замінюють всі знайдені входження. Різниця в тому, як вони шукають:
replace() — шукає звичайний текст (літерал):
String s = "apple.banana.orange";
System.out.println(s.replace(".", "/"));
// "apple/banana/orange" — крапка шукається буквально
replaceAll() — шукає за регулярним виразом:
String s = "apple.banana.orange";
System.out.println(s.replaceAll(".", "/"));
// "/////////////////" — крапка в regex = "будь-який символ"!
Головна пастка: Назва replace оманлива — він замінює ВСІ входження, а не перше. Для заміни тільки першого є replaceFirst().
🟡 Middle Level
Порівняння методів
| Метод | Що шукає | Замінює | Швидкість |
|---|---|---|---|
replace(char, char) |
Символ | Всі | Найшвидший |
replace(CharSequence, CharSequence) |
Літерал (текст) | Всі | Швидкий |
replaceAll(String regex, String replacement) |
Регулярний вираз | Всі | Повільний |
replaceFirst(String regex, String replacement) |
Регулярний вираз | Перше | Повільний |
Практичне застосування
// replace — для звичайного тексту
s.replace("old", "new"); // Літеральна заміна
s.replace('a', 'b'); // Заміна символів
// replaceAll — для патернів
s.replaceAll("\\d+", "#"); // Всі цифри → #
s.replaceAll("\\s+", " "); // Множинні пробіли → один
// replaceFirst — тільки перший збіг
s.replaceFirst("\\d", "#"); // Тільки першу цифру
Типові помилки
-
Помилка:
replaceAll("user.name", "admin")— крапка = будь-який символ Рішення:replaceAll(Pattern.quote("user.name"), "admin")абоreplace("user.name", "admin") -
Помилка: Думати, що
replaceміняє тільки перше Рішення:replaceміняє ВСІ. Для першого —replaceFirst -
Помилка:
$в replacement рядку дляreplaceAllРішення:$1,$2— посилання на групи. Для літерального$використовуйтеMatcher.quoteReplacement("$")
🔴 Senior Level
Internal Implementation
replace(CharSequence, CharSequence) — через Pattern.LITERAL replace(char, char) — окремий метод, прямий scan byte[], БЕЗ Pattern
public String replace(CharSequence target, CharSequence replacement) {
// Перевірка Fast Path для single char
if (target instanceof String && replacement instanceof String) {
String tgt = (String) target;
String repl = (String) replacement;
if (tgt.length() == 1 && repl.length() == 1) {
return replace(tgt.charAt(0), repl.charAt(0));
}
}
// Через Pattern/Literal replacement — без regex engine
return Pattern.compile(target.toString(), Pattern.LITERAL)
.matcher(this)
.replaceAll(Matcher.quoteReplacement(replacement.toString()));
}
replaceAll(String regex, String replacement):
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
Ключова різниця: replace використовує Pattern.LITERAL (literal matching), replaceAll — повноцінний regex engine.
Архітектурні Trade-offs
replace (literal):
- Плюси: Немає regex overhead, безпечний зі спецсимволами, передбачуваний
- Мінуси: Немає патернів, груп, backreferences
replaceAll (regex):
- Плюси: Повна потужність regex (групи, lookahead, backreferences)
- Мінуси: Компіляція regex, алокації, спецсимволи потрібно екранувати
Performance Comparison
| Метод | Механізм | Алокації | 1M викликів |
|---|---|---|---|
replace('a', 'b') |
Прямий прохід по byte[] | 1 String | ~10ms |
replace("ab", "cd") |
indexOf + копіювання | 1 String + Pattern | ~50ms |
replaceAll("a.b", "x") |
Regex compile + match | Pattern + Matcher + String | ~300ms |
replaceAll(precompiled, "x") |
Тільки match | Matcher + String | ~80ms |
// Цифри приблизні, залежать від JVM і довжини рядків.
Edge Cases
- Regex metacharacters в replaceAll:
s.replaceAll("path\\to\\file", "/new"); // \t = tab, \f = form feed! s.replaceAll(Pattern.quote("path\\to\\file"), "/new"); // Літерально - Replacement з
$:"price: $100".replaceAll("\\$100", "$200"); // $2 — backreference, помилка! "price: $100".replaceAll("\\$100", Matcher.quoteReplacement("$200")); // OK - Empty replacement:
"abcabc".replace("b", ""); // "acac" "abcabc".replaceAll("b", ""); // "acac" - No match:
"hello".replace("x", "y"); // "hello" — без змін, новий об'єкт!
Production Experience
Сценарій: User input sanitization:
// ПОГАНО — користувач ввів "user[admin]" → regex syntax error
String sanitized = input.replaceAll(userInput, "[REDACTED]");
// ГАРАЗД — literal replacement
String sanitized = input.replace(userInput, "[REDACTED]");
// АБО — екранування
String sanitized = input.replaceAll(Pattern.quote(userInput), "[REDACTED]");
Сценарій 2: Log masking (100K log lines/sec):
replaceAll("\\b\\d{4}\\b", "XXXX")для маскування PIN → ~300ms на 100K- Pre-compiled pattern → ~80ms
- Ручний парсинг → ~15ms (але складніше підтримувати)
Best Practices для Highload
- Дефолт:
replace()для літерального тексту — безпечніше і швидше - Для regex: pre-compiled
Pattern+matcher.replaceAll() - Для спецсимволів в replacement:
Matcher.quoteReplacement() - Для ultra-low-latency: ручна реалізація через
indexOf+StringBuilder - Security: завжди
Pattern.quote()для user input в regex
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
replace()— літеральна заміна (не regex), замінює ВСІ входженняreplaceAll()— заміна за регулярним виразом, замінює ВСІ входженняreplaceFirst()— заміна за regex, тільки перший збіг- Назва
replaceоманлива — він замінює все, а не одне входження - Спецсимволи в replacement для
replaceAll:$1,$2— backreference на групи - Для літерального
$в replacement:Matcher.quoteReplacement("$")
Часті уточнюючі запитання:
- Чому
replaceAll(".", "/")замінює все? — Крапка в regex = “будь-який символ”. Використовуйтеreplace(".", "/")для літеральної заміни. - Що швидше:
replace()чиreplaceAll()? —replace()швидше — немає regex overhead.replace('a','b')— прямий прохід по масиву. - Чи міняє
replace()тільки перше входження? — Ні, попри назву,replace()міняє ВСІ входження. - Як замінити тільки перше входження тексту? —
replaceFirst()— але це regex. Для літерального —indexOf+substring.
Червоні прапорці (НЕ говорити):
- ❌ “
replace()міняє тільки перше входження” — міняє ВСІ, попри назву - ❌ “
replaceAll()швидший заreplace()” — навпаки, regex повільніший - ❌ “Можна передавати user input напряму в
replaceAll()” — regex injection, використовуйтеPattern.quote() - ❌ “
$в replacement — це просто символ” — це backreference на групи regex
Пов’язані теми:
- [[15. Як працює метод split()]]
- [[7. Що відбувається при конкатенації рядків через оператор +]]
- [[4. Чому String є незмінним]]