Rozdział 5 - Pętle - Typ String i metoda charAt oraz pętle

Typ String, przechowujący ciągi znaków, pozwala na pobranie z niego znaku o podanym indeksie.

Indeksy znaków zaczynają się od 0, a nie od 1, więc pierwszy znak przechowywany w zmiennej typu String znajduje się pod indeksem 0, a indeks ostatniego znaku to liczba znaków pomniejszona o 1.

Dla przykładu, mając następującą zmienną typu String:

String komunikat = "Ala ma kota";

Znaki składające się na wartość zmiennej komunikat mają następujące indeksy:

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

Zauważmy, iż:

  • pierwszy znak, czyli 'A', ma indeks 0, a nie 1.
  • ostatni znak, czyli 'a', ma indeks równy długości całego stringu (tzn. liczby znaków, które zawiera) minus 1, czyli 10 (ponieważ wszystkich znaków jest 11). Wynika to z faktu, że indeksy rozpoczynają się od 0, a nie 1.

Aby otrzymać znak znajdujący się na danej pozycji, stosujemy metodę charAt, której podajemy jako argument indeks znaku, który chcemy pobrać.

Spójrzmy na przykład, w którym wypisujemy na ekran pierwszy znak zmiennej typu String:

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

    System.out.println(komunikat.charAt(0));
  }
}

Ten program wypisuje na ekran jedną literę:

W

Skorzystaliśmy w nim z metody charAt do pobrania pierwszego znaku w stringu, czyli tego, który ma indeks 0. Znak jest zwracany i służy jako argument dla instrukcji wypisującej tekst na ekran.

A co stanie się, jeżeli podamy nieprawidłowy indeks? Na przykład, zapytamy o znak o indeksie 100, gdy zmienna typu String będzie zawierała krótszy tekst?

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

    System.out.println(komunikat.charAt(100));
  }
}

Ten program skompiluje się bez błędów, jednak po uruchomieniu go wystąpi błąd i działanie programu zakończy się. Na ekranie zobaczymy następujący błąd:

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 100 at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:47) at java.base/java.lang.String.charAt(String.java:702) at WypiszZnakCharAtNieprawidlowyIndeks.main( WypiszZnakCharAtNieprawidlowyIndeks.java:5)

Błąd wystąpił, ponieważ podany indeks jest nieprawidłowy – w zmiennej komunikat nie ma znaku o indeksie 100, ponieważ string ten zawiera jedynie 6 znaków. Komunikat błędu jest jasny: String index out of range: 100.

Zwróćmy uwagę na bardzo ważny aspekt tego programu. Program skompilował się bez błędów, tzn. kompilator poprawnie nie stwierdził żadnych problemów w kodzie.

Problem w programie nie wynika z błędnej składni programu, którą kompilator jest w stanie wychwycić – problem wynika z logicznego błędu, który uwidacznia się dopiero po uruchomieniu programu.

Skoro znaki w stringach mają indeksy, które są kolejnymi liczbami całkowitymi, to moglibyśmy skorzystać z funkcjonalności pętli for, która w prosty sposób umożliwi nam odwoływanie się do kolejnych znaków w stringu. Będziemy jednak potrzebować sposobu, by dowiedzieć się, z ilu znaków łańcuch tekstowy się składa.

W jednym z zadań do rozdziału trzeciego o zmiennych (Liczba znaków w słowie) należało skorzystać z metody, która zwraca liczbę znaków przechowywanych w zmiennej typu String. Ta metoda to length.

Spójrzmy na użycie obu metod, charAt oraz length, by "rozstrzelić" zapisany napis (tzn. wstawić po każdym znaku tekstu dodatkową spację):

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

    for (int i = 0; i < tekst.length(); i++) {
      System.out.print(tekst.charAt(i) + " ");
    }
  }
}

Użyliśmy metody length, by pobrać długość tekstu, a także metody charAt, by pobrać znak na danej pozycji (pamiętajmy, że pierwszy znak ma indeks 0!). Wynik:

A l a m a k o t a

Zastosowana pętla for przechodzi po wszystkich liczbach, które są indeksami znaków w stringu tekst, zaczynając od 0. Zauważmy, że warunek pętli korzysta z operatora < a nie <=. Gdybyśmy skorzystali z operatora <=, to zmienna i w ostatniej iteracji miałaby wartość 11 – tyle wynosi liczba znaków w zmiennej tekst i taką wartość zwraca tekst.length() – spowodowałoby to błąd, ponieważ indeks ostatniego znaku to 10, a nie 11 – dlatego musieliśmy skorzystać z operatora <.

Spójrzmy na jeszcze jeden przykład – wypisujemy w nim tekst od końca:

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

    for (int i = tekst.length() - 1; i >= 0; i--) {
      System.out.print(tekst.charAt(i));
    }
  }
}

Zwróćmy tutaj uwagę, iż zmienna i, zdefiniowana w pętli, zaczęła od indeksu ostatniego znaku w zmienej tekst, którego indeks równy jest liczbie znaków w tym stringu minus 1, o czym już wspominaliśmy. W kroku pętli zmniejszamy ten indeks o 1. W ciele pętli wypisujemy znak pod aktualnym indeksem, co skutkuje, że idziemy od końca tekstu do początku. Pętla kończy działanie, gdy zmienna i przekroczy indeks pierwszego znaku, czyli 0.

Wynik:

atok am alA

Porównywanie znaków zwracanych przez charAt

Wykorzystywana przez nas metoda charAt zwraca pojedynczy znak, czyli wartość typu podstawowego char, a nie łańcuch tekstowy typu String.

Powoduje to, że możemy porównywać znaki zwracane przez metodę charAt z innymi znakami za pomocą operatora ==. Nie musimy, a nawet nie możemy, stosować w takim przypadku metody equals. Spójrzmy na przykład programu, który zlicza samogłoski w wyrazie podanym przez użytkownika:

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

public class ZliczSamogloski {
  public static void main(String[] args) {
    System.out.println("Podaj slowo:");
    String slowo = getString();

    int liczbaSamoglosek = 0;

    for (int i = 0; i < slowo.length(); i++) {
      char znak = slowo.charAt(i);

      if (znak == 'a' || znak == 'e' || znak == 'i' ||
          znak == 'u' || znak == 'y' || znak == 'o') {
        liczbaSamoglosek++;
      }
    }

    System.out.println(
        "Slowo " + slowo + " ma " + liczbaSamoglosek + " samoglosek."
    );
  }

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

Program pobiera od użytkownika słowo i w pętli for przechodzi przez każdy znak, z którego się składa. Aktualny znak przypisujemy do zmiennej o nazwie znak, którą następnie przyrównujemy do samogłosek za pomocą operatora ==. Nie korzystamy z metody equals, ponieważ w tym przypadku nie działamy na stringach, lecz na znakach.

Warunek sprawdzający, czy znak to samogłoska, to złożone wyrażenie wykorzystujące operator logiczny || (lub) – jeżeli aktualny znak będzie którąkolwiek z samogłosek, to warunek będzie spełniony, więc zwiększymy liczbę znalezionych samogłosek o jeden za pomocą instrukcji liczbaSamoglosek++;.

Po przejściu przez całe słowo, wypisujemy na ekran informację o znalezionej liczbie samogłosek.

Przykładowe uruchomienie tego programu:

Podaj slowo: programowanie Slowo programowanie ma 6 samoglosek.

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.