13 października 2008

SCJP - Klasa String

Kolejny zastrzyk wiedzy przed egzaminem SCJP – kontynuacja dzieła zapowiedzianego w artykule „Przygotowania do SCJP czas zacząć”. Dziś będzie o typie napisowym reprezentowanym przez klasę String.

Zacznijmy od rozwiania ewentualnych wątpliwości natury fundamentalnej. Otóż typ napisowy w języku Java nie jest typem prostym. Co prawda przywykliśmy do tego, że obiekty tworzymy z użyciem operatora ‘new’, a więc inaczej niż zazwyczaj tworzone są String’i, ale nie zmienia to faktu, że każdy String jest obiektem. Zerknijmy na poniższy kod.
public class MyClass {
public static void main(String[] args) {
String a = new String("Ola ma kota"); // oczywiście można tak

String b = "A ula psa"; // ale możemy użyć także skróconej formy

System.out.print(b.getClass());
}
}

Uruchomienie tego programu spowoduje wyświetlenie napisu „class java.lang.String”. Czy jednak obydwa pokazane powyżej sposoby utworzenia obiektu napisu są równoważne? Otóż nie. Konstrukcja ‘new String(…)’ zawsze skutkuje utworzeniem nowego obiektu, natomiast forma skrócona, z bezpośrednim użyciem literału spowoduje utworzenie nowego obiektu tylko wówczas, gdy obiekt reprezentujący dany napis jeszcze nie istnieje. A gdzie ma istnieć? Otóż Wirtualna Maszyna Javy utrzymuje pulę String’ów – w specjalnym, przeznaczonym do tego celu obszarze pamięci – z nadzieją, że przyczyni się to do optymalizacji czasu wykonania i ilości potrzebnej pamięci. Pierwsze użycie literału napisowego spowoduje utworzenie nowego obiektu oraz umieszczenie go w owej puli, zaś każde następne użycie odnosić się będzie do tegoż właśnie obiektu z puli (kolejny obiekt nie jest tworzony). Przekonajmy się o tym analizując poniższy program,
public class MyClass {
public static void main(String[] args) {
String a = "Ola";
String b = "Ola";

System.out.println("a == b : " + (a == b));

String c = new String("Ola");
String d = new String("Ola");

System.out.println("c == d : " + (c == d));
}
}

który uruchomiony wyświetli napis
a == b : true
c == d : false

Zatem zgadza się. Referencja ‘a’ i ‘b’ wskazują na ten sam obiekt (wyrażenie ‘a == b’ ma wartość ‘true’) zaś ‘c’ i ‘d’ już nie (wyrażenie ‘c == d’ ma wartość ‘false’).

Kolejnym ważnym faktem odnośnie obiektów klasy String jest, że są one niemodyfikowalne (ang. immutable). Obiekt klasy String reprezentuje pewien stały, dokładnie jeden napis. Jeśli potrzebujemy obiektu, który reprezentuje inny napis, to najzwyczajniej w świecie potrzebujemy nowego obiektu klasy String. Zerknijmy na poniższy przykład,
public class MyClass {
public static void main(String[] args) {
String a = "Ola";
String b = a;

System.out.println("a == b : " + (a == b));

a += " ma kota";

System.out.println("a == b : " + (a == b));
}
}

który uruchomiony wyświetli napis
a == b : true
a == b : false

Prześledźmy teraz, jak to się dzieje. W pierwszej linii deklarujemy referencję ‘a’ i przypisujemy jej nowo utworzony obiekt reprezentujący napis „Ola”. Obiekt ten ląduje w puli obiektów o której mówiliśmy przed chwilą. Następnie deklarujemy referencję ‘b’ i przypisujemy jej ten sam obiekt, który przypisaliśmy do zmiennej ‘a’. Nic więc dziwnego, że pierwszą linią wyświetloną przez program jest ‘a == b : true’. Następnie, do zmiennej ‘a’ chcemy dodać sufiks „ ma kota”. Wiemy, że obiekty klasy String są niemodyfikowalne a więc operator ‘+=’ nie może zmienić wartości obiektu wskazywanego przez zmienną ‘a’. Co zatem się dzieje? Otóż tworzony jest nowy obiekt inicjowany wartością „Ola ma kota” i to ten właśnie obiekt jest przypisywany do zmiennej ‘a’. Zmienna ‘a’ wskazuje zatem teraz na nowy, zupełnie inny obiekt niż zmienna ‘b’, stąd porównanie ‘a == b’ ma wartość ‘false’.

String to klasa zupełnie podstawowa, stąd niezbędna jest – także z perspektywy egzaminu SCJP – znajomość jej najpopularniejszych metod. Poniżej zaprezentowano listę tychże, wraz z opisem i przykładem wywołania.

public char charAt(int index) - zwraca znak znajdujący się na zadanej argumentem wywołania pozycji w String’u, przy czym należy pamiętać, że tak jak tablice, indeks zaczyna się od liczby 0, tj. wyrażenie
"abcdef".charAt(1) 
ma wartość ‘b’

public String concat(String sufix) - zwraca nowy obiekt typu String, który ma wartość taką jak obiekt dla którego wywołano metodę z doklejonym jako sufiks argumentem wywołania, np. wyrażenie
"abcdef".concat("ghij")
ma wartość ‘abcdefghij’

public boolean equalsIgnoreCase(String str) – metoda pozwala stwierdzić, czy porównywane napisy (ten, dla którego wywołano metodę i ten przekazany jako argument) są takie same (leksykograficznie), przy czym przy porównywaniu ignoruje się „wielkość liter” (litery małe i duże są utożsamiane), np. wyrażenie
"abc".equalsIgnoreCase("AbC")
ma wartość ‘true’

public int length() - metoda ta zwraca długość napisu reprezentowanego przez obiekt, tj. ilość jego znaków, np. wyrażenie
"abc".length()
ma wartość ‘3’

public String replace(char old, char new) - metoda zwraca nowy obiekt typu String, powstały poprzez zastąpienie (w String’u dla którego wywołano metodę) każdego wystąpienia znaku przekazanego jako pierwszy argument, przez znak przekazany jako drugi argument, np. wyrażenie
"ababab".replace('b', 'c') 
ma wartość ‘acacac’

public String substring(int begin) - zwraca String’a powstałego po odrzuceniu początkowych znaków ze String’a dla którego wywołano metodę; pierwszym znakiem zwracanego String’a będzie ten, który znajduje się na pozycji przekazanej jako argument wywołania, np. wyrażenie
"abcdef".substring(1) 
ma wartość ‘bcdef’

public String substring(int begin, int end) - zachowuje się jak metoda opisana powyżej, z tym, że obcina także końcówkę napisu; wartość przekazana jako drugi argument wywołania to indeks pierwszego znaku który nie znajdzie się w zwracanym String’u, np. wyrażenie
"abcdef".substring(1, 3) 
ma wartość ‘bc’

public String toLowerCase() - zwraca String’a, który powstał po zamienieniu wszystkich dużych liter String’a dla którego wywołano metodę na odpowiadające litery małe, np. wyrażenie
"aBcDe".toLowerCase()
ma wartość ‘abcde’

public String toUpperCase() - metoda analogiczna do metody opisanej powyżej, z tym, że litery małe zamienia na duże a nie na odwrót, np. wyrażenie
"aBcDe".toUpperCase()
ma wartość ‘ABCDE’

public String trim() - zwraca napis powstały ze Stinga użytego do wywołania metody po obcięciu białych znaków znajdujących się na początku i końcu tegoż Stringa, np. wyrażenie
"    przed tabulacja, po spacja ".trim()
ma wartość ‘przed tabulacja, po spacja’

2 komentarze:

Kazik Pogoda pisze...

W kontekście owej puli Stringów warto wspomnieć jeszcze o metodzie intern()

Deklaracja:

String a = "Ola";

odpowiada funkcjonalnie:

String a = (new String("Ola")).intern();

Anonimowy pisze...

Super artykuł :) programuję w javie już 3 lata i nie byłem świadomy takich rzeczy :]
Dzięki za artykuł :)
Zapraszam na swoją stronę www.konkursy-hack.pl