Rozdział 7 - Metody - Metody typu string

Typ String oferuje wiele przydatnych metod – zaraz zaznajomimy się z kilkoma z nich.

Nie mylmy stringów ze znakami! Stringi zapisujemy w cudzysłowach, np. "Wijtacie!", a znaki (typ char) w apostrofach, na przykład 'a'!

Przypomnienie metod length i charAt oraz indeksów znaków

Jednym z typów referencyjnych, z którego do tej pory korzystaliśmy, jest typ String, który służy do przechowywania ciągu znaków.

Typ String jest szczególnym typem w języku Java z kilku powodów – o typie String opowiemy sobie dokładniej w rozdziale o klasach.

Typ String oferuje wiele przydatnych metod, które możemy używać w naszych programach. Korzystaliśmy już z metod:

  • length, aby poznać liczbę znaków, z których składał się String,
  • charAt, metody, która zwracała znak na danej pozycji w stringu (pamiętajmy, że indeks pierwszego znaku to 0, a nie 1!).

Aby skorzystać z którejś z metod typu String, po nazwie zmiennej należy napisać kropkę, a następnie nazwę metody z nawiasami i ewentualnymi argumentami – zasady są takie same, jak przy wywoływaniu napisanych przez nas metod z poprzednich podrozdziałów:

Nazwa pliku: StringLengthCharAt.java
public class StringLengthCharAt {
  public static void main(String[] args) {
    String komunikat = "Witaj Swiecie!";

    int liczbaZnakow = komunikat.length();
    System.out.println("Liczba znakow to: " + liczbaZnakow);

    // musimy odjac 1 od wyniku length(), poniewaz
    // indeks znakow zaczyna sie od 0, a nie 1!
    char ostatniZnak = komunikat.charAt(komunikat.length() - 1);
    System.out.println("Ostatni znak to: " + ostatniZnak);
  }
}
  1. W programie tworzymy zmienną typu String.
  2. Następnie, przypisujemy do zmiennej liczbaZnakow wynik wywołania metody length na zmiennej komunikat, która zwróci liczbę znaków w stringu "Witaj Swiecie!", po czym wypisujemy liczbę znaków na ekran.
  3. Do zmiennej ostatniZnak zapisujemy ostatni znak w stringu. Aby to zrobić, używamy metody charAt, która przyjmuje jako argument indeks znaku, który chcemy pobrać ze stringa. Jako, że indeks znaków zaczyna się od 0, a nie 1, musimy od wyniku metody length odjąć 1, aby nie wyjść poza zakres stringa. Jest to zobrazowane poniżej:
Numer znaku 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Indeks znaku 0 1 2 3 4 5 6 7 8 9 10 11 12 13
W i t a j S w i e c i e !

Metoda charAt (a także, jak zobaczymy za chwilę – wiele innych metod typu String) przyjmuje jako argument indeks znaku w stringu, a nie jego numer w kolejności – różnica pomiędzy tymi dwoma pojęciami jest taka, że indeks zaczynamy liczyć od 0, zamiast od 1.

Metoda length zwraca liczbę znaków, których w stringu Witaj Swiecie! jest 14 – gdybyśmy spróbowali użyć wyniku tej metody jako argument do metody charAt, to zapytalibyśmy o zwrócenie znaku o indeksie 14, który nie istnieje! Zgodnie z powyższą tabelą, indeksem ostatniego znaku jest 13, dlatego od wyniku metody length musieliśmy odjąć 1.

W wyniku działania program, na ekranie zobaczymy:

Liczba znakow to: 14 Ostatni znak to: !

Przykłady użycia metod typu String

Poza metodami length i charAt, typ String oferuje wiele innych, przydatnych metod. Poniżej znajdują się opisy kilku z nich.

toLowerCase, toUpperCase

Te dwie metody służą do zwrócenia stringa, na rzecz którego metoda została wykonana, z, odpowiednio, wszystkie literami zamienionymi na małe litery (toLowerCase), lub na wielkie litery (toUpperCase). Ich sygnatury są następujące:

  • String toLowerCase()
  • String toUpperCase()

Uwaga: te metody nie modyfikują stringa, na rzecz którego zostały wywołane – zamiast tego, zwracają jego kopię ze wszystkimi literami zamienionymi na małe/wielkie litery. Co to w zasadzie oznacza? Spójrzmy na poniższy przykład:

Nazwa pliku: StringToUpperCase.java
public class StringToUpperCase {
  public static void main(String[] args) {
    String komunikat = "Witajcie po raz 20!";

    komunikat.toUpperCase();

    System.out.println(komunikat);
  }
}

Który z komunikatów zobaczymy na ekranie?

  • Witajcie po raz 20!
  • czy może:
  • WITAJCIE PO RAZ 20!

Na ekranie zobaczymy tekst Witajcie po raz 20!. Stało się tak dlatego, że użycie metody toUpperCase nie spowodowało zmiany zawartości zmiennej komunikat – zamiast tego, metoda ta zwróciła tekst WITAJCIE PO RAZ 20!, ale ani nie przypisaliśmy wyniku tej metody do żadnej zmiennej, ani nigdzie z tej wartości nie skorzystaliśmy.

Jest to bardzo ważna informacja – metody toUpperCase i toLowerCase nie zmieniają w żaden sposób oryginalnego stringa, na którym zostały wywołane. Mało tego – żadna z metod typu String tego nie robi! Dlaczego tak się dzieje – dowiemy się w rozdziale o klasach.

W kolejnym przykładzie nie popełniamy już tego błędu:

Nazwa pliku: StringToUpperLowerCase.java
public class StringToUpperLowerCase {
  public static void main(String[] args) {
    String komunikat = "Witajcie po raz 20!";

    String wielkieLitery = komunikat.toUpperCase();

    System.out.println(wielkieLitery);
    System.out.println(komunikat.toLowerCase());
  }
}

W tym przykładzie, wynik działania metody toUpperCase, użytej na zmiennej komunikat, przypisujemy do zmiennej wielkieLitery. Następnie, wypisujemy zawartość zmiennej wielkieLitery – tym razem na ekranie zobaczymy tekst z wielkimi literami.

Na końcu programu wypisujemy także na ekran wynik działania toLowerCase, dzięki czemu zobaczymy na ekranie ponownie wiadomość, jednak będzie ona miała pierwszą literę zamienioną na małą literę:

WITAJCIE PO RAZ 20! witajcie po raz 20!

Warto tutaj także zwrócić uwagę, że liczba 20 oraz wykrzyknik ! nie zmieniły się – metody toLowerCase i toUpperCase zmieniają jedynie litery.

Pamiętajmy: wszelkie operacje wykonywane za pomocą metod typu String nie powodują zmiany stringa, na rzecz którego metoda została wykonana – zamiast tego, zwracana jest zmodyfikowana kopia!

endsWith, startsWith, contains

Sygnatury tych trzech metod są następujące:

  1. boolean startsWith(String prefix)
  2. boolean endsWith(String suffix)
  3. boolean contains(CharSequence s)

Każda z powyższych metod odpowiada na pewne pytanie, zwracając prawdę bądź fałsz za pomocą wartości o typie boolean (true / false).

  1. Pierwsza metoda, startsWith, zwraca true, jeżeli string, na rzecz którego metoda została wywołana, zaczyna się od podanego jako argument ciągu znaków. W przeciwnym razie zwracana wartość to false.
  2. Metoda endsWith działa podobnie jak startsWith z tym, że sprawdzany jest koniec stringu.
  3. Metoda contains działa podobne, jak dwie poprzednie metody z tym, że sprawdzany jest cały string – jeżeli przesłany jako argument ciąg znaków jest zawarty w stringu, na rzecz którego wywołujemy tę metodę, zwrócona zostanie wartość true. Jeżeli nie, to false.

Spójrzmy na przykład użycia powyższych metod:

Nazwa pliku: StringStartsWithEndsWithContains.java
public class StringStartsWithEndsWithContains {
  public static void main(String[] args) {
    String s = "Ala ma kota";

    System.out.println(
        "Czy string zaczyna sie od 'Ala'? " + s.startsWith("Ala")
    );
    System.out.println(
        "Czy string zaczyna sie od 'ala'? " + s.startsWith("ala")
    );

    System.out.println(
        "Czy string konczy sie na 'kota'? " + s.endsWith("kota")
    );

    System.out.println("Czy zawiera 'ma'? " + s.contains("ma"));
    System.out.println("Czy zawiera 'kot'? " + s.contains("kot"));
    System.out.println("Czy zawiera 'ala'? " + s.contains("ala"));
    System.out.println(
        "Czy zawiera 'ala'? " + s.toLowerCase().contains("ala")
    );
  }
}

W powyższym kodzie kilkukrotnie używamy przedstawionych wcześniej metod. Spójrzmy na wyniki działania programu:

Czy string zaczyna sie od 'Ala'? true Czy string zaczyna sie od 'ala'? false Czy string konczy sie na 'kota'? true Czy zawiera 'ma'? true Czy zawiera 'kot'? true Czy zawiera 'ala'? false Czy zawiera 'ala'? true

Interesujące są dwa następujące wyniki:

Czy string zaczyna sie od 'ala'? false Czy zawiera 'ala'? false

Dlaczego otrzymaliśmy takie wyniki? Metoda startsWith zwróciła true dla argumentu Ala, ale false dla argumentu ala. Podobnie metoda contains – zwróciła false dla argumentu ala, chociaż taki ciąg znaków znajduje się w sprawdzanym stringu.

Takie wyniki tych metod wynikają z faktu, że małe i wielkie litery w stringach są rozróżniane i traktowane są jako różne znaki – dlatego zobaczyliśmy na ekranie takie a nie inne wyniki.

Jeżeli chcemy zignorować małe i wielkie litery, możemy skorzystać z poznanych już metod toLowerCase i toUpperCase, by sprawdzić, czy, na przykład, string zawiera pewien ciąg znaków, tak jak w powyższym programie w ostatnim przykładzie:

System.out.println(
    "Czy zawiera 'ala'? " + s.toLowerCase().contains("ala")
);

Skorzystaliśmy z metody toLowerCase, która zwróciła zawartość zmiennej s ze wszystkimi literami zamienionymi na małe litery – następnie, od razu wywołaliśmy na zwróconym stringu metodę contains (użycie metod w ten sposób, jedna po drugiej, to tzw. method chaining). Dzięki temu, wywołaliśmy metodę contains z argumentem ala nie na stringu Ala ma kota, lecz na stringu ala ma kota – dlatego na ekranie zobaczyliśmy wynik true.

equals, equalsIgnoreCase

Metody equals oraz equalsIgnoreCase służą do porównywania stringów – ich sygnatury są następujące:

  • boolean equals(Object anObject)
  • boolean equalsIgnoreCase(String anotherString)

Jeżeli string, który prześlemy jako argument do equals lub equalsIgnoreCase jest taki sam, jak string, na rzecz którego wywołujemy jedną z tych metod, to zwrócona zostanie wartość true, a w przeciwnym razie false. Metody te różnią się tym, że druga z nich, equalsIgnoreCase, ignoruje wielkość znaków (stringi "Ala" i "ala" są dla niej taki same, w przeciwieństwie do meteody equals).

Te dwie metody mają jeszcze kilka różnic – na przykład, czym jest tajemniczy typ Object, którego oczekuje metoda equals? Dowiemy się w kolejnym rozdziale! Na ten moment wystarczy nam informacja, że do metody equals możemy po prostu przesłać string.

Zobaczmy przykład użycia dwóch powyższych metod:

Nazwa pliku: StringEqualsEqualsIgnoreCase.java
import java.util.Scanner;

public class StringEqualsEqualsIgnoreCase {
  public static void main(String[] args) {
    System.out.print("Podaj slowo: ");

    String slowo = getString();

    if (slowo.equals("kot")) {
      System.out.println("Podane slowo to kot.");
    } else {
      System.out.println("Podano slowo inne niz kot");
    }

    if (slowo.equalsIgnoreCase("Kot")) {
      System.out.println(
        "Podane slowo (bez uwzglednienia wielkosci znakow) to kot."
      );
    } else {
      System.out.println(
        "Podano slowo inne niz kot (nawet po nie braniu pod uwage wielkosci znakow)."
      );
    }
  }

  public static String getString() {
    return new Scanner(System.in).next();
  }
}

W tym programie pobieramy od użytkownika słowo i porównujemy je do słowa kot dwukrotnie – raz za pomocą equals, a drugi raz za pomocą equalsIgnoreCase, by nie uwzględnić przy porównaniu wielkości znaków. Spójrzmy na wyniki dwukrotnego uruchomienia tego programu:

Podaj slowo: KOT Podano slowo inne niz kot Podane slowo (bez uwzglednienia wielkosci znakow) to kot.
Podaj slowo: pies Podano slowo inne niz kot Podano slowo inne niz kot (nawet po nie braniu pod uwage wielkosci znakow).

Pytanie: po co nam dwie powyższe metody? Czy nie możemy po prostu porównywać stringów za pomocą operatora porównania ==, tak jak porównujemy np. liczby?

Jest to podchwytliwe pytanie, ponieważ możemy używać operatora == by porównywać stringi, ale nigdy nie powinniśmy tego robić – dlaczego tak jest dowiemy się w rozdziale o klasach. Użycie operatora porównania do porównywania stringów zazwyczaj zwraca inne wyniki, niż byśmy się spodziewali:

Nazwa pliku: StringOperatorPorownania.java
import java.util.Scanner;

public class StringOperatorPorownania {
  public static void main(String[] args) {
    System.out.print("Podaj slowo: ");

    String slowo = getString();

    if (slowo == "kot") {
      System.out.println("Wpisales slowo kot.");
    } else {
      System.out.println("Wpisales slowo inne niz kot.");
    }
  }

  public static String getString() {
    return new Scanner(System.in).next();
  }
}

W powyższym programie pobieramy od użytkownika słowo i zapisujemy je w zmiennej o nazwie slowo. Następnie, używamy operatora porównania by sprawdzić, czy słowo, jakie podał użytkownik, to kot.

Zobaczmy co się stanie, gdy podamy słowo kot:

Podaj slowo: kot Wpisales slowo inne niz kot.

Skoro podaliśmy słowo kot, to dlaczego zobaczyliśmy na ekranie niespodziewany komunikat? Wynika to z nieprawidłowego sposobu porównywania stringów – zamiast skorzystać z metody equals bądź equalsIgnoreCase, użyliśmy operatora ==, którego nigdy nie powinniśmy stosować do porównywania stringów. W rozdziale o klasach dowiemy się z czego wynika ten wymóg.

Zapamiętajmy – gdy będziemy musieli porównywać dwa łańcuchy tekstowe (stringi), powinniśmy używać metody equals (lub equalsIgnoreCase) zamiast używać operator porównania == .

Pamiętajmy także, by nie mylić stringów ze znakami – stringi zapisujemy w cudzysłowach " ", a znaki w apostrofach ' ' – porównywanie znaków za pomocą operatora porównania ==, w przeciwieństwie do porównywania stringów, jest poprawne.

replace, substring, split

Trzema ostatnimi metodami typu String, które poznamy, są:

  1. String replace(CharSequence oldChar, CharSequence newChar)
  2. String substring(int beginIndex, int endIndex)
  3. String[] split(String regex)
  1. Metoda replace zwraca kopię stringa, na rzecz którego ją wywołujemy, ze wszystkimi wystąpieniami tekstu przesłanego jako pierwszy argument zastąpionymi tekstem podanym jako drugi argument.
  2. Metoda substring zwraca fragment stringa, na rzecz którego ją wywołujemy. Fragment ten zaczyna się od indeksu przesłanego jako pierwszy argument, a kończy na indeksie przesłanym jako drugi argument. Zwracany fragment nie zawiera znaku o indeksie wskazywanym jako drugi argument (przypomnijmy: indeks pierwszego znaku to 0).
  3. Ostatnia metoda, split, zwraca tablicę stringów – dzieli ona na części string, na rzecz którego wywołujemy tę metodę, używając przesłanego argumentu jako separator. Na przykład, wywołanie split na stringu raz,dwa,trzy z argumentem , (przecinek) zwróciłoby tablicę stringów z trzema elementami: "raz" "dwa" "trzy".

Spójrzmy na użycie każdej z tych metod:

Nazwa pliku: StringReplace.java
public class StringReplace {
  public static void main(String[] args) {
    String tekst = "Ala ma kota";

    String zmianaImienia = tekst.replace("Ala", "Jola");
    String bezSpacji = tekst.replace(" ", "");

    System.out.println("Tekst z innym imieniem: " + zmianaImienia);
    System.out.println("Tekst bez spacji: " + bezSpacji);
  }
}

Do zmiennej zmianaImienia zapisujemy zawartość zmiennej tekst z zamienionym ciągiem znaków Ala na ciąg znaków Jola.

Kolejna zmienna, bezSpacji, otrzymuje wynik działania metody replace, do której jako pierwszy argument podaliśmy spację, którą chcemy zamienić na... pustego stringa – w takim przypadku wszelkie spacje zostaną po prostu usunięte w wynikowym stringu i na ekranie zobaczymy:

Tekst z innym imienie: Jola ma kota Tekst bez spacji: Alamakota

Uwaga: małe i wielkie litery mają znaczenie podczas szukania tekstu do zastąpienia! Gdybyśmy przy pierwszym z powyższych wywołań metody replace podali jako pierwszy argument "ala", to zostałby zwrócony niezmieniony string Ala ma kota.

Czas na przykład użycia metody substring:

Nazwa pliku: StringSubstring.java
public class StringSubstring {
  public static void main(String[] args) {
    String tekst = "Ala ma kota";

    String pierwszeSlowo = tekst.substring(0, 3);
    String drugieSlowo = tekst.substring(4, 6);
    String trzecieSlowo = tekst.substring(7, tekst.length());

    System.out.println("Pierwsze slowo: " + pierwszeSlowo);
    System.out.println("Drugie slowo: " + drugieSlowo);
    System.out.println("Trzecie slowo: " + trzecieSlowo);
  }
}

W wyniku działania programu na ekranie zobaczymy:

Pierwsze slowo: Ala Drugie slowo: ma Trzecie slowo: kota

Każda z trzech zmiennych otrzymuje po jednym słowie ze stringa zapisanego w zmiennej tekst. Osiągamy to poprzez użycie metody substring z odpowiednimi argumentami – indeksem pierwszego znaku, od którego chcemy pobrać fragment oryginalnego stringa, oraz indeksu znaku końcowego, który nie będzie w zwróconym fragmencie (substringu).

Poniższa tabela obrazuje, dlaczego użyliśmy takich a nie innych argumentów w każdym z wywołań metody substring:

Indeks 0 1 2 3 4 5 6 7 8 9 10
Znak A l a m a k o t a

Aby pobrać pierwsze słowo, zaczynamy od pierwszego znaku – jego indeks to 0. Chcemy pobrać trzy pierwsze znaki. Metoda substring jako drugi argument oczekuje indeksu znaku, przed którym ma zakończyć zwracany fragment – ten znak pod indeksem podanym jako drugi argument nie jest włączany do zwracanego wyniku. Dlatego, aby pobrać pierwsze słowo, jako drugi argument podajemy indeks spacji (który wynosi 3), a nie indeks małej litery a (indeks 2).

Analogicznie dzieje się z drugim słowem – zaczynamy od indeksu 4, ponieważ pod tym indeksem znajduje się pierwsza litera drugiego słowa (m). Jako koniec podajemy indeks spacji pod indeksem 6 – ponownie, ta spacja nie będzie w zwróconym fragmencie tekstu.

Ostatni przykład jest najbardziej interesujący – otóż nie odejmujemy od wartości zwróconej przez metodę length liczby 1 (tak jak robiliśmy to już kilkukrotnie w innych przykładach w tym rozdziale)! Czy nie spowoduje to błędu? Nie – w końcu metoda substring nie zwraca znaku znajdującego się pod indeksem przekazanym jako drugi argument. Argument ten służy jedynie do wyznaczenia granicy zwracanego fragmentu tekstu. Tak się składa, że dla fragmentu, który jest końcowym fragmentem oryginalnego tekstu, granicą jest indeks następujący po ostatnim indeksie w oryginalnym stringu. Ten indeks to 11, ponieważ ostatni znak ma indeks równy 10. Taką właśnie wartość zwraca metoda length (ponieważ string Ala ma kota składa się z 11 znaków).

Na końcu przyjrzyjmy się metodzie split:

Nazwa pliku: StringSplit.java
public class StringSplit {
  public static void main(String[] args) {
    String tekst = "Ala ma kota";

    // podziel tekst - jako separator uzyj spacji
    String[] slowa = tekst.split(" ");

    for (int i = 0; i < slowa.length; i++) {
      System.out.println("Slowo nr " + (i + 1) + " to: " + slowa[i]);
    }


    String zwierzetaPoPrzecinku = "kot,pies,,chomik";

    // podziel tekst - jako separator uzyj przecinka
    String[] zwierzeta = zwierzetaPoPrzecinku.split(",");

    for (int i = 0; i < zwierzeta.length; i++) {
      System.out.println(
          "Zwierze nr " + (i + 1) + " to: " + zwierzeta[i]
      );
    }
  }
}

W tym przykładzie najpierw używamy metody split by podzielić tekst Ala ma kota na słowa, które będą zwrócone w postaci tablicy stringów. Jako separator do oddzielenia słów od siebie podajemy spację jako argument do metody split.

W kolejnym przykładzie rozdzielamy tekst kot,pies,,chomik używając jako separatora przecinka. Zauważmy, że pomiędzy drugim a trzecim przecinkiem nie ma żadnego znaku – spowoduje to, że w tablicy stringów, która zostanie zwrócona, trzeci element będzie pustym stringiem, co widzimy na ekranie po uruchomieniu powyższego programu:

Slowo nr 1 to: Ala Slowo nr 2 to: ma Slowo nr 3 to: kota Zwierze nr 1 to: kot Zwierze nr 2 to: pies Zwierze nr 3 to: Zwierze nr 4 to: chomik

Trzeci element tablicy zwierzeta to pusty string.

Powyżej przedstawionych zostało jedynie kilka z metod, jakie oferuje typ String. Informacje o tych i pozostałych metodach można znaleźć w oficjalnej dokumentacji typu String na stronie: JavaDoc String

Dokumentacja znajdująca się na podanej wyżej stronie została wygenerowana automatycznie – czy i my możemy (i jak to zrobić) dokumentować nasze metody?

Komentarze (2):

  1. Dla metody substrig na ekranie chyba powinno sie pokazac:
    Pierwsze slowo: Ala
    Drugie slowo: ma
    Trzecie slowo: kota
    Poza tym super kurs i dziekuje za udostepnienie go za free.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Nie musisz podawać swojego imienia, e-mailu, ani strony www, aby opublikować komentarz. Komentarze muszą zostać zatwierdzone, aby były widoczne na stronie.