В чём особенность метода 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 является иммутабельным]]