Podstawowy typ tekstowy w języku Java to klasa java.lang.String. Klasa String reprezentuje niemodyfikowalną sekwencję znaków.
Z jednej strony klasa String to klasa jak każda inna, a z drugiej, jest to typ absolutnie kluczowy, którego używamy w sposób często wyjątkowy. Zacznijmy od tego, że w sposób absolutnie wyjątkowy tworzymy instancje tej klasy.
Aby utworzyć obiekt typu String nie posługujemy się standardowym operatorem new, tylko literałami, zupełnie jak w przypadku typów prostych (prymitywnych). Aby więc utworzyć obiekt typu String reprezentujący napis „Witaj świecie!” napiszemy:
String helloWorld = "Witaj świecie!";
Klasa String udostępnia szereg metod umożliwiających wykonywanie prostych operacji tekstowych, takich jak np. zamienianie wielkich liter na małe czy zastępowanie określonych znaków innymi. Pełna lista dostępnych metod wraz z opisami znajduje się w dokumentacji klasy String, jednak zanim zabierzemy się do lektury tejże zapoznajmy się z prostym, ale niezmiernie ważnych faktem – obiekty klasy String są niemodyfikowalne.
Co to oznacza, że obiekty klasy String są niemodyfikowalne? W jaki sposób zatem metoda toLowerCase() może zmienić wielkie litery na małe (bo właśnie to ona robi)? Otóż metoda ta, podobnie jak wszystkie inne metody klasy String nie tyle zmienia istniejący obiekt, co tworzy nowy obiekt, który jest wynikiem wykonanej operacji. Dotychczas istniejący obiekt pozostaje bez zmian. Zerknijmy na poniższy program:
public class TestClass { public static void main(String[] args) { String strA = "AbCdE"; String strB = strA.toLowerCase(); System.out.println(strA); System.out.println(strB); } }
W pierwszej linii programu tworzymy referencję strA typu String oraz obiekt typu String reprezentujący napis „AbCdE” i przypisujemy ten obiekt do referencji.
W drugiej linii uruchamiamy dla obiektu wskazywanego przez referencję strA metodę toLowerCase(). Metoda ta zamienia wszystkie wielkie litery na małe, jednak nie w ten sposób, że modyfikuje istniejący obiekt, bo to jest niemożliwe. Wywołanie metody toLowerCase() powoduje utworzenie nowego obiektu typu String, który to obiekt reprezentuje napis „abcde”. Obiekt ten jest zwracany jako wynik działania metody i przypisujemy go do referencji strB. O tym, że oryginalny obiekt, na który wskazuje referencja strA się nie zmienił, możemy przekonać się wyświetlając jego wartość na ekranie. Uruchommy ten program i przekonajmy się, że rezultatem jego działania będzie wyświetlenie napisów „AbCdE” oraz „abcde”.
W analogiczny do przedstawionego powyżej sposób działają wszystkie metody klasy String. Ponieważ obiektów klasy String nie da się zmienić, zatem operacje te zwracają nowe obiekty, przekształcone zgodnie z algorytmem uruchomionej metody.
Klasa String jest z jednej strony klasą jak każda inna, w związku z czym możemy dla jej instancji wywoływać metody tak jak dla instancji każdej innej klasy, jednak jednocześnie jest klasą bardzo specjalną.
Oprócz tego, że instancje klasy String tworzymy w sposób niestandardowy, to możemy też w sposób niestandardowy wykonywać dla instancji tej klasy pewną często wykonywaną operację, a mianowicie operację konkatenacji. Aby skleić (skonkatenować) dwa teksty możemy – posługując się operacją String concat(String str) – napisać:
public class TestClass { public static void main(String[] args) { String strA = "AbCdE"; String strB = strA.concat("fGhI"); System.out.println(strA); System.out.println(strB); } }
Ale możemy także skorzystać ze wspomnianej już wyjątkowości, i użyć operatora +, zupełnie tak jakbyśmy napisy do siebie dodawali. Możemy więc zamiast powyższego napisać:
public class TestClass { public static void main(String[] args) { String strA = "AbCdE"; String strB = strA + "fGhI"; System.out.println(strA); System.out.println(strB); } }
Wiemy już, że metoda concat(…) nie zmienia obiektu wskazywanego przez referencję strA tylko tworzy nowy obiekt – obiekt który reprezentuje sklejone napisy. Dokładnie to samo robi operator +. Nie ma żadnych wyjątków od reguły, że obiekty typu String są niemodyfikowalne.
Skoro każdorazowe użycie operatora + czy operacji concat(…) powoduje utworzenie nowego obiektu, to wielokrotne wykonywanie tych operacji (np. w pętli) musi być (i jest) dość kosztowne. Z tego powodu nie powinniśmy w takim przypadku używać klasy String, tylko innej klasy, istniejącej właśnie po to, by łańcuchy znaków budować. Klasa String służy zasadniczo do reprezentowania łańcuchów, natomiast do budowanie tekstów służy klasa java.lang.StringBuilder.
Przykładowo, poniższy program buduje tekst zawierający kolejne liczby dwucyfrowe podzielne przez liczbę przekazaną jako argument wywołania:
String dividables(int div) { StringBuilder strBuilder = new StringBuilder(); for (int x = 10; x < 100; x++) if (x % div == 0) strBuilder.append(x).append(" "); return strBuilder.toString().trim(); }
W pętli FOR powyższego programu iterujemy po kolejnych liczbach z przedziału od 10 do 99 i jeśli dana liczba jest podzielna przez liczbę przekazaną jako argument wywołania to doklejamy ją za pomocą operacji append(…).
Operacja append(…) klasy StringBuilder dokleja (konkatenuje) przekazany argument do końca tekstu, bez tworzenia nowych obiektów i innych zbędnych operacji.
Po doklejeniu liczby doklejamy jeszcze spację, aby oddzielić od siebie poszczególne wartości. Na koniec używamy metody toString() aby przekonwertować obiekt klasy StringBuilder na obiekt klasy String. Używamy jeszcze metody trim() z klasy String aby pozbyć się spacji doklejonej za ostatnią liczbą. Oczywiście wywołanie metody trim() spowoduje utworzenie dodatkowej instancji klasy String, ale jest to operacja wykonywana jednokrotnie, więc nie mamy się czym martwić. Co dokładnie robi metoda trim()? Odszukajmy w ramach ćwiczenia opis we wspomnianej na początku dokumentacji klasy.
Jakoś nie do końca mnie przekonuje wyjaśnienie „niemodyfikowaln ości”. Bo jak Twoje wyjaśnienie działa dla poniższych przykładów:
1. z wykorzystaniem „toLowerCase()”
public class TestString_01
{
public static void main(String[] args)
{
String strX = „AbCdE”;
System.out.println(strX);
strX = strX.toLowerCase();
System.out.println(strX);
}
}
2. z wykorzystaniem konkatenacji „concat()”
public class TestString_02
{
public static void main(String[] args)
{
String strY = „Ala ma”;
System.out.println(strY);
strY = strY.concat(” kota”);
System.out.println(strY);
}
}
Proponuję jeszcze raz przeczytać z uwagą lekcję pod tytułem „Referencje”. Dobre zrozumienie tamtego materiału powinno pomóc w zrozumieniu tego co jest napisane tutaj. W podanym powyżej przykładzie żaden obiekt typu String nie został zmodyfikowany. Instrukcja strX.toLowerCase(); spowodowała UTWORZENIE NOWEGO, INNEGO obiektu typu String. Stary obiekt pozostał przy tym bez zmian. Proponuję przed linią:
strX = strX.toLowerCase();
dopisać:
String oldStrX = strX;
i potem na końcu programu:
System.out.println(oldStrX );
to wtedy może wszystko się rozjaśni.
Witam. Zaczynając ten kurs wiedziałem, że nie jest to łatwe, ale przebrnąłem do tego momentu bez problemów. Teraz czuję, że moja logika stanowczo sprzeciwia się temu co widzę. Wierzę, że wszystko, co jest napisane powyżej jest prawdą, ale przecież nie chodzi o to, abym nauczył się regułek na wiarę. A więc problem zaczyna się w komentarzu kolegi powyżej. Naocznie jakby udowadnia, że obiekt strX został zmieniony. (Bo czy nie można tak powiedzieć skoro jego wywołanie daje dwa inne efekty?) Następnie poszedłem za Pana radą i dopisałem linijkę tekstu. Ale wydaje mi się, że ona nie zachowuje niezmienionego pierwszego obiektu, jedynie… Czytaj więcej »
” Jako, że w łańcuchach nie można zmieniać znaków, obiekty klasy String są określane jako niezmienialne. Podobnie jak liczba 3 jest zawsze liczbą 3, tak łańcuch „Cześć!” zawsze będzie szeregiem znaków C z e ś ć !. Tego nie można zmienić. Można jednak, o czym się przekonaliśmy, mienić zawartość zmiennej przechowującej ten łańcuch, sprawiając aby odnosiła się do innego łańcucha. Tak samo jak możemy zdecydować że zmienna liczbowa, która do tej pory przechowywała wartość 4 będzie przechowywać wartość 5.” To pomogło mi to zrozumieć, może komus się przyda 🙂
Jeśli mamy: String strX = „ALA”; to strX zawiera ADRES( inaczej RFERENCJĘ) do miejsca w pamięci, gdzie jest umieszczona WARTOŚĆ ”ALA” (te 3 znaki, bez cudzysłowów) a potem: strX = strX.toLowerCase(); samo: strX.toLowerCase(); ta metoda (toLowerCase()) wykonana zostanie z wykorzystaniem WARTOŚCI „ALA” obiektu strX i – UWAGA – kompilator wygeneruje NIENAZWANY obiekt typu String, którego WARTOŚCIĄ bedzie już „ala”. Kompilator umieści ten string nie w miejscu gdzie jest jest WARTOŚĆ stringu strX, ale w jakimś innym (czyli wartości strX nie zmieni). Po zakończeniu instukcji (czyli po średniku) kompilator „zapomina” adres tego chwilowego obiektu i przepadło (np. strX.toLowerCase(); – bez przypisania).… Czytaj więcej »