1. Niezmienność łańcucha: przyjaciel czy wróg?
W Javie klasa String — niezmienna (immutable). Oznacza to, że po utworzeniu łańcucha nie można go zmienić. Za każdym razem, gdy „modyfikujesz” łańcuch, na przykład dodając do niego coś przez + lub concat(), w rzeczywistości tworzony jest nowy obiekt, a stary trafia na śmietnik historii (i pamięci).
Przykład:
String s = "Hello";
s = s + " world!";
System.out.println(s); // Hello world!
Wygląda, jakby łańcuch s się zmienił, ale w rzeczywistości powstał nowy łańcuch "Hello world!", a stary "Hello" pozostaje w pamięci, dopóki nie usunie go zbieracz śmieci. Jeśli takich operacji jest dużo — na przykład w pętli — program zaczyna zwalniać i zużywać nadmiarową pamięć.
Wyobraź sobie, że budujesz wieżę z klocków i za każdym razem, gdy chcesz dodać nowy klocek, musisz przebudować całą wieżę od nowa. Mało ekonomiczne, prawda? Tak właśnie działa zwykły String w Javie przy częstych zmianach.
2. StringBuilder: szybki „budowniczy” łańcuchów
Klasa StringBuilder (pakiet java.lang, nie trzeba importować) to specjalne narzędzie do efektywnego składania i modyfikowania łańcuchów. Jest mutowalna (mutable): można dodawać, usuwać, wstawiać znaki i podciągi bez tworzenia nowego obiektu przy każdej operacji.
Analogia:
Jeśli String to betonowa płyta, to StringBuilder to klocki LEGO: dodajesz i zdejmujesz elementy do woli, nie rozbierając wszystkiego od nowa.
Jak utworzyć StringBuilder
StringBuilder sb = new StringBuilder(); // pusty
StringBuilder sb2 = new StringBuilder("Wartość początkowa");
Podstawowe metody
| Metoda | Opis | Przykład użycia |
|---|---|---|
|
Dodać na koniec łańcuch, liczbę, znak itd. | |
|
Wstawić wartość w podanej pozycji | |
|
Usunąć znaki od pozycji start (włącznie) do end (wyłącznie) | |
|
Zastąpić fragment łańcucha inną zawartością | |
|
Odwrócić łańcuch znaków | |
|
Przekształcić do zwykłego łańcucha | |
|
Przyciąć lub dopełnić łańcuch do podanej długości | |
Przykład użycia
StringBuilder sb = new StringBuilder();
sb.append("Cześć, ");
sb.append("świecie!");
System.out.println(sb); // Cześć, świecie!
sb.insert(7, "Java "); // wstawimy "Java " po "Cześć, "
System.out.println(sb); // Cześć, Java świecie!
sb.replace(8, 12, "inny"); // zamienimy "Java" na "inny"
System.out.println(sb); // Cześć, inny świecie!
sb.reverse();
System.out.println(sb); // !eiceiwś ynni ,śćezC
3. StringBuffer: starszy brat z bezpieczeństwem wielowątkowym
Jaka jest różnica między StringBuilder a StringBuffer?
- StringBuilder — szybki, ale niebezpieczny w środowisku wielowątkowym (niesynchronizowany).
- StringBuffer — wolniejszy, ale bezpieczny dla wątków (zsynchronizowany).
Jeśli Twoja aplikacja działa w jednym wątku — używaj StringBuilder (szybszy). Jeśli wiele wątków może modyfikować ten sam łańcuch — używaj StringBuffer.
4. Kiedy używać StringBuilder zamiast String?
Scenariusze
- Częste modyfikacje łańcucha (dodawanie, usuwanie, wstawianie) w pętli lub przy składaniu dużych tekstów.
- Składanie łańcucha z tablicy/listy (CSV, HTML, raporty itd.).
- Parsowanie i przetwarzanie tekstu z dużą liczbą operacji na łańcuchu.
Przykład: składanie łańcucha z tablicy
Zły sposób (przez String i +):
String[] names = {"Iwan", "Piotr", "Maria"};
String result = "";
for (int i = 0; i < names.length; i++)
{
result += names[i];
if (i < names.length - 1)
{
result += ", ";
}
}
System.out.println(result);
Dobry sposób (przez StringBuilder):
String[] names = {"Iwan", "Piotr", "Maria"};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.length; i++)
{
sb.append(names[i]);
if (i < names.length - 1)
{
sb.append(", ");
}
}
System.out.println(sb.toString());
Różnica: w pierwszym przypadku na każdym kroku powstaje nowy łańcuch, w drugim — rozbudowujemy jeden obiekt StringBuilder.
5. Porównanie wydajności: String vs StringBuilder
// Przez String
long t1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 10000; i++)
{
s += i + " ";
}
long t2 = System.currentTimeMillis();
System.out.println("String: " + (t2 - t1) + " ms");
// Przez StringBuilder
t1 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.append(i).append(" ");
}
s = sb.toString();
t2 = System.currentTimeMillis();
System.out.println("StringBuilder: " + (t2 - t1) + " ms");
Wniosek: w większości przypadków StringBuilder działa wielokrotnie szybciej przy licznych konkatenacjach.
6. Przydatne niuanse
Czy można używać metod String?
StringBuilder ma własne metody. Aby uzyskać łańcuch — wywołaj toString().
StringBuilder sb = new StringBuilder("Hello");
String s = sb.toString(); // teraz s to zwykły łańcuch
Czy można porównywać StringBuilder przez equals?
Uwaga: sb1.equals(sb2) porównuje referencje, a nie zawartość. Porównuj tak:
if (sb1.toString().equals(sb2.toString()))
{
// zawartość jest taka sama
}
Czy można przekazać StringBuilder do System.out.println?
Tak. System.out.println wywoła toString() automatycznie.
Czy można odczytywać znaki po indeksie?
Tak, użyj charAt(int index), jak w zwykłym łańcuchu.
7. Typowe błędy podczas pracy ze StringBuilder i StringBuffer
Błąd nr 1: porównywanie dwóch StringBuilder przez equals lub operator ==. Te sprawdzenia porównują referencje, a nie zawartość. Użyj toString() i porównuj łańcuchy.
Błąd nr 2: zapominanie o wywołaniu toString() tam, gdzie oczekiwany jest String (zwracanie z metody, logowanie, przekazanie do API).
Błąd nr 3: używanie StringBuilder do kilku prostych konkatenacji. Zapis "Hello, " + name jest czytelny i wydajny.
Błąd nr 4: konkatenacja String w pętli przez +. To nieefektywne czasowo i pamięciowo — użyj StringBuilder.
Błąd nr 5: mylenie StringBuffer i StringBuilder bez potrzeby. Jeśli nie ma współdzielonej modyfikacji z wielu wątków — wybierz StringBuilder.
Błąd nr 6: bezrefleksyjnie przycinać zawartość StringBuilder metodą setLength(). Sprawdź poprawność nowej długości — dane po niej zostaną utracone.
GO TO FULL VERSION