Temat java replace sprowadza się do kilku metod, które wyglądają podobnie, ale działają inaczej: jedne podmieniają dosłowne znaki i fragmenty, a inne uruchamiają silnik wyrażeń regularnych. W praktyce to rozróżnienie decyduje o tym, czy kod będzie krótki i czytelny, czy zacznie płatać figle przy kropkach, dolarach i spacji. Pokażę ci, kiedy użyć której metody, jak uniknąć błędów i kiedy lepiej sięgnąć po StringBuilder.
Najważniejsze różnice między metodami zamiany tekstu
-
replace(char, char)ireplace(CharSequence, CharSequence)działają dosłownie, bez regexu. -
replaceAllireplaceFirsttraktują pierwszy argument jako wyrażenie regularne. -
Stringjest niemutowalny, więc każda zamiana zwraca nowy obiekt. - Jeśli replacement ma znak
$albo\, użyjMatcher.quoteReplacement. - Przy wielu zmianach w jednym tekście często wygodniejszy jest
StringBuilder.replace.
Najpierw wybierz między zamianą dosłowną a regexem
Ja zwykle rozdzielam ten temat na dwa światy. W pierwszym chcesz podmienić dokładnie to, co widzisz w tekście: znak, słowo, prefiks albo konkretny fragment. W drugim opisujesz wzorzec, czyli regułę typu „wszystkie spacje”, „pierwszy separator” albo „ciąg cyfr”.
| Metoda | Co robi | Czy używa regexu | Najlepsze zastosowanie | Na co uważać |
|---|---|---|---|---|
replace(char, char) |
Zamienia wszystkie wystąpienia jednego znaku | Nie | Proste korekty znaków, np. myślniki na podkreślenia | Nie zastąpi fragmentu wieloznakowego |
replace(CharSequence, CharSequence) |
Zamienia dosłowny fragment tekstu | Nie | Literalne podmiany, np. protokół, prefiks, słowo | Nie zadziała jak wzorzec |
replaceAll(String, String) |
Zamienia wszystkie dopasowania regexu | Tak | Normalizacja, maskowanie, zamiany oparte na regułach | Łatwo pomylić zwykły tekst z regexem |
replaceFirst(String, String) |
Zamienia pierwsze dopasowanie regexu | Tak | Usuwanie prefiksu, pierwszej etykiety, pierwszego separatora | W replacement są znaczące $ i \
|
StringBuilder.replace(int, int, String) |
Podmienia zakres znaków w mutowalnym buforze | Nie | Wiele zmian w tym samym tekście, pętle, składanie wyniku | Wymaga znajomości indeksów |
Jeśli potrzebujesz zwykłej zamiany fragmentu, regex jest zwykle zbędny. Jeśli potrzebujesz reguły, która opisuje wiele różnych przypadków naraz, zwykły replace przestaje wystarczać. To rozróżnienie prowadzi wprost do konkretnego wyboru API, więc dalej rozbijam je na praktyczne scenariusze.
Jak działa replace bez regexu
Metody bez regexu są najbezpieczniejsze, bo robią dokładnie to, co sugeruje nazwa. replace(char, char) podmienia pojedynczy znak, a replace(CharSequence, CharSequence) działa na dosłownym ciągu znaków. Nie ma tu żadnych metaznaków, grup ani wyjątków wynikających z interpretacji wzorca.
To właśnie dlatego w prostych zadaniach wybieram je najczęściej. Jeśli chcesz zamienić myślniki na podkreślenia albo zmienić http:// na https://, nie ma sensu odpalać regexu tylko dlatego, że brzmi „bardziej profesjonalnie”.
String fileName = "raport-final-v1.txt";
String normalized = fileName.replace('-', '_');
String url = "http://jscwiczenia.pl";
String secureUrl = url.replace("http://", "https://");
Warto też pamiętać, że String w Javie jest niemutowalny. Każda z takich operacji zwraca nowy tekst, a oryginał zostaje bez zmian. Dla jednorazowej zamiany to zaleta, bo kod jest prosty i przewidywalny. Przy większej liczbie zmian ten model zaczyna jednak kosztować więcej kopii, dlatego za chwilę pokażę przykład, w którym lepiej sprawdza się StringBuilder.
Przykłady, które pokazują różnicę w kodzie
Najłatwiej zrozumieć ten temat na kilku krótkich przykładach. W praktyce nie chodzi o zapamiętanie nazw metod, tylko o to, żeby od razu widzieć, kiedy zamiana jest literalna, a kiedy opiera się na regule.
String title = "backend-dev";
String withSpaces = title.replace('-', ' ');
// "backend dev"
String path = "/api/v1/users";
String withoutLeadingSlash = path.replaceFirst("^/", "");
// "api/v1/users"
String sentence = "Ala ma kota";
String compact = sentence.replaceAll("\\s+", " ");
// "Ala ma kota"
Pierwszy przykład jest najprostszy: pojedynczy znak zamieniony na inny. Drugi pokazuje, że replaceFirst działa na wzorcu, więc przydaje się do wycinania pierwszego dopasowania, a nie pierwszego literalnego fragmentu. Trzeci to klasyczny case normalizacji białych znaków, gdzie regex faktycznie oszczędza kod.
replace. Gdy jednak musisz zostać przy replaceFirst lub replaceAll, wtedy wzorzec warto zacytować przez Pattern.quote(...). To samo dotyczy replacementu z dolarem lub backslashem, tylko tam używa się Matcher.quoteReplacement(...).
W skrócie: jeśli widzisz prostą, literalną podmianę, wybierasz prostą metodę. Jeśli w grę wchodzi reguła dopasowania, dopiero wtedy zaczyna się zabawa z regexem.
Kiedy replaceAll i replaceFirst naprawdę mają sens
replaceAll jest dobry wtedy, gdy chcesz zmienić wszystkie dopasowania wzorca, a nie tylko jedno konkretne wystąpienie. replaceFirst wybierasz, gdy interesuje cię wyłącznie pierwsze dopasowanie. W obu przypadkach pierwszy argument nie jest zwykłym tekstem, tylko wyrażeniem regularnym, więc musisz myśleć w kategoriach wzorców, a nie „szukaj i podmień”.
replaceAll do normalizacji i maskowania
To jest najczęstszy i najbardziej praktyczny scenariusz. W logach, formularzach i danych wejściowych często chcesz ujednolicić zapis: usunąć nadmiarowe spacje, zamaskować fragmenty danych albo przestawić kolejność segmentów tekstu.
String phone = "600-123-456";
String masked = phone.replaceAll("\\d(?=\\d{2})", "X");
// "XXX-XXX-456"
String date = "2026-06-20";
String polishDate = date.replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3.$2.$1");
// "20.06.2026"
Drugi przykład pokazuje, po co w ogóle istnieją grupy przechwytujące. Dzięki $1, $2 i $3 możesz przebudować wynik bez ręcznego składania stringów. To jest jeden z tych przypadków, w których regex robi realną robotę, zamiast tylko komplikować kod.
replaceFirst do wycinania tylko pierwszego dopasowania
Jeżeli chcesz usunąć prefiks, zmienić pierwszy znacznik albo zastąpić tylko pierwszy separator, replaceFirst bywa najczytelniejszy. Ja używam go wtedy, gdy dalsze wystąpienia mają już zostać nienaruszone.
String tag = "[draft] Artykuł do poprawy";
String cleaned = tag.replaceFirst("^\\[draft\\]\\s*", "");
// "Artykuł do poprawy"
W takim zadaniu nie musisz budować kilku instrukcji warunkowych. Jedna reguła regexu załatwia sprawę i od razu widać intencję kodu. Jeśli ten sam wzorzec masz zamiar wykorzystywać wielokrotnie, wtedy warto rozważyć wcześniejszą kompilację przez Pattern, żeby nie robić tego za każdym razem od nowa.
Przeczytaj również: Java Iterable - Jak działa? Różnice, przykłady i błędy
Jak nie potknąć się o znaki specjalne
Tu pojawia się najwięcej błędów. W regexie kropka oznacza „dowolny znak”, znak dolara odnosi się do grupy, a backslash ma znaczenie ucieczki. Do tego dochodzi jeszcze drugi poziom escapowania w stringach Javy. W efekcie zapis, który wygląda niewinnie, potrafi zachować się zupełnie inaczej, niż zakładasz.
import java.util.regex.Matcher;
String text = "Wartość: 49$";
String safeReplacement = Matcher.quoteReplacement("99$");
String updated = text.replaceAll("49\\$", safeReplacement);
// "Wartość: 99$"
Jeśli replacement jest generowany dynamicznie, Matcher.quoteReplacement oszczędza sporo nerwów. Używam go szczególnie wtedy, gdy tekst pochodzi z zewnętrznego źródła i nie mam pewności, czy nie zawiera znaków specjalnych. To mały detal, ale w praktyce często rozróżnia kod stabilny od kodu, który wybucha na danych z produkcji.
Ta część tematu jest ważna, bo najwięcej problemów nie wynika z samego zastąpienia tekstu, tylko z nieporozumienia między literalnym stringiem a regexem. Stąd już krok do typowych pułapek.
Najczęstsze pułapki przy zamianie tekstu
Najbardziej zdradliwe jest założenie, że wszystkie metody zamiany działają podobnie. W Java nic bardziej mylnego. Wystarczy jeden znak specjalny albo zbyt luźny wzorzec i wynik przestaje mieć cokolwiek wspólnego z tym, co chciałeś osiągnąć.
-
Kropka w regexie oznacza dowolny znak, więc
replaceAll(".", "-")zamieni praktycznie cały tekst, a nie tylko kropki. -
Replacement nie jest zwykłym stringiem w metodach regexowych, więc znak
$może odwoływać się do grupy. - Escapowanie działa podwójnie: raz w stringu Javy, drugi raz w regexie.
- Zamiana działa sekwencyjnie od początku do końca, więc nakładające się fragmenty nie są traktowane jak osobny przypadek.
Dobry przykład tego ostatniego zachowania to sytuacja, w której zamieniasz aa na b w napisie aaa. Wynik będzie ba, a nie ab, bo silnik przechodzi przez tekst z lewej do prawej i nie cofa się, żeby złapać nakładające się trafienia. To drobiazg, ale potrafi całkowicie zmienić wynik przy prostych algorytmach czyszczenia danych.
Jeśli chcesz zamienić literalny znak kropki, zwykle wybierasz replace, a nie replaceAll. Jeśli chcesz użyć regexu, to lepiej od razu pisać wzorzec świadomie, zamiast liczyć, że JVM domyśli się twojej intencji. Taka dyscyplina oszczędza więcej czasu niż późniejsze debugowanie pozornie prostych tekstów.
Kiedy StringBuilder.replace daje lepszy efekt
Gdy modyfikujesz jeden tekst wielokrotnie, StringBuilder często wygrywa z łańcuchem kolejnych wywołań na String. Powód jest prosty: String jest niemutowalny, więc każda zamiana tworzy nowy obiekt. W małych przykładach to nie ma większego znaczenia, ale w pętli, przy dłuższych danych albo przy składaniu odpowiedzi HTML robi się zauważalne.
StringBuilder sb = new StringBuilder("Hello, guest! Today is draft.");
sb.replace(7, 12, "admin");
sb.replace(24, 29, "final");
// "Hello, admin! Today is final."
To narzędzie działa po indeksach, więc nie szuka tekstu za ciebie. Z tego powodu jest świetne, gdy już znasz pozycje zmian, na przykład po wcześniejszym parsowaniu stringa albo po wyliczeniu zakresów. Jeśli nie znasz indeksów i chcesz po prostu podmienić fragment, zwykły replace będzie czytelniejszy.
Warto też pamiętać, że StringBuilder jest zazwyczaj lepszym wyborem niż StringBuffer w kodzie jednowątkowym, bo nie dokłada synchronizacji, której zwykle nie potrzebujesz. To detal, ale w praktyce właśnie takie detale odróżniają wygodny kod od kodu napisanego „na wszelki wypadek”.
Co zostaje w praktyce, gdy wybierasz metodę zamiany
Jeśli miałbym sprowadzić cały temat do jednej zasady, powiedziałbym tak: najpierw sprawdź, czy potrzebujesz literalnej zamiany, a dopiero potem sięgaj po regex. W większości prostych przypadków wystarczy replace. Gdy naprawdę pracujesz na wzorcu, replaceAll i replaceFirst dają dużo większą kontrolę nad wynikiem.
-
replace(char, char)do jednego znaku. -
replace(CharSequence, CharSequence)do dosłownego fragmentu. -
replaceAlldo wszystkich dopasowań wzorca. -
replaceFirstdo pierwszego dopasowania wzorca. -
StringBuilder.replacedo wielu zmian w jednym, mutowalnym buforze.
W kodzie produkcyjnym najbardziej cenię nie „sprytną” metodę, tylko metodę, którą da się od razu przeczytać i bez stresu utrzymać za miesiąc. Właśnie dlatego przy zamianie tekstu w Javie zwykle wygrywa prostota, a regex warto włączać tylko wtedy, gdy naprawdę rozwiązuje konkretny problem.