Rozdział 5 - Pętle - Zagnieżdżanie pętli

Pętle można zagnieżdżać – w ciele jednej pętli możemy skorzystać z kolejnej pętli. Poniższy przykład wypisuje na ekran tabliczkę mnożenia:

Nazwa pliku: TabliczkaMnozenia.java
public class TabliczkaMnozenia {
  public static void main(String[] args) {
    for (int i = 1; i <= 6; i++) {
      for (int j = 1; j <= 6; j++) {
        int wynik = i * j;

        if (wynik >= 10) {
          System.out.print(wynik + " ");
        } else {
          // dla jednocyfrowych wynikow dodajemy spacje
          // na poczatku, by wynik byl ladnie sformatowany
          System.out.print(" " + wynik + " ");
        }
      }

      System.out.println(); // pusta linia
    }
  }
}

Wynik jest następujący:

1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36

W każdym pojedynczym obiegu zewnętrznej pętli for wykonywana jest cała wewnętrzna pętla for – łącznie w tym programie odbędzie się 6 iteracji pętli zewnętrznej i 36 iteracji pętli wewnętrznej (ponieważ cała pętla wewnętrzna wykona się sześć razy). Zmienna j w pętli wewnętrznej sześć razy przejdzie przez wszystkie liczby od 1 do 6.

W każdej iteracji pętli wewnętrznej obliczamy wynik mnożenia obu zmiennych pętli: i oraz j. Następnie, wypisujemy wynik w jednej linii na ekranie (korzystamy z print zamiast println, dzięki czemu kolejne komunikaty będą wypisywane w tej samej linii). Jeżeli aktualnie policzony wynik mnożenia jest liczbą jednocyfrową, to przed wypisywanym wynikiem dodajemy jeszcze znak spacji, aby wyniki były równo wypisane.

Po tym, jak wykona się pętla wewnętrzna, wykonujemy jeszcze jedną instrukcję w obiegu pętli zewnętrznej:

System.out.println(); // pusta linia

Instrukcja ta powoduje wypisanie na ekran znaku nowej linii, dzięki czemu, gdy w kolejnym obiegu zewnętrznej pętli rozpocznie się wykonywanie pętli wewnętrznej, kolejne wyniki będą wypisywane w linii poniżej.

Spójrzmy, jak będą zmieniać się wartości zmiennych w powyższym programie dla dwóch pierwszych obiegów pętli zewnętrznej:

  1. Inicjalizowana jest pętla zewnętrzna – zmiennej i nadawana jest wartość 1.
  2. Sprawdzany jest warunek pętli zewnętrznej i <= 6, który jest spełniony.
  3. Wykonywane jest ciało pętli zewnętrznej:
    1. Inicjalizowana jest pętla wewnętrzna – zmiennej j nadawa jest wartość 1.
    2. Sprawdzany jest warunek pętli wewnętrznej – jest spełniony.
    3. Wykonywane jest ciało pętli wewnętrznej – obliczany jest wynik i * j, czyli 1 * 1, a następnie wynik 1 jest wypisywany na ekran.
    4. Pętla wewnętrzna kończy iterację – wykonywana jest instrukcja kroku j++.
    5. Ponownie sprawdzany jest warunek pętli wewn. – j wynosi 2, warunek jest spełniony.
    6. W ciele pętli wewnętrznej obliczany i wypisywany jest wynik i * j, czyli 1 * 2.
    7. Pętla wewnętrzna kończy iterację – wykonywana jest instrukcja kroku j++.
    8. Ponownie sprawdzany jest warunek pętli wewn. – j wynosi 3, warunek jest spełniony.
    9. W ciele pętli wewnętrznej obliczany i wypisywany jest wynik i * j, czyli 1 * 3.
    10. Pętla wewnętrzna kończy iterację – wykonywana jest instrukcja kroku j++.
    11. Pętla wewnętrzna kontynuuje działanie do momentu, aż warunek j <= 6 przestaje być spełniony.
  4. Gdy pętla wewnętrzna kończy swoje działanie, w pętli zewnętrznej zostaje jeszcze do wykonania instrukcja System.out.println();
  5. Dopiero w tym miejscu kończy się pierwsza iteracja zewnętrznej pętli – wykonywana jest instrukcja kroku pętli zewnętrznej, czyli i++i będzie miało teraz wartość 2.
  6. Sprawdzany jest warunek pętli zewnętrznej i <= 6. Jest spełniony, gdyż i ma wartość 2.
  7. Ponownie wykonywane jest ciało pętli zewnętrznej:
    1. Ponownie inicjalizowana jest pętla wewnętrzną – zmiennej j nadawana jest wartość 1.
    2. Sprawdzany jest warunek pętli wewnętrznej.
    3. Wykonywane jest ciało pętli wewnętrznej – obliczany jest wynik i * j, czyli 2 * 1,
    4. tak dalej.

Dalsze działanie programu jest takie samo, jak opisano wcześniej – pętla wewnętrzna działa do czasu, aż jej warunek przestaje być spełniony. Nastepuje zakończenie pętli wewnętrznej, kończy się iteracja pętli zewnętrznej i następuje ponowna jej iteracja, do czasu, aż warunek pętli zewnętrznej przestaje być prawdziwy.

Pętlę zagnieżdżone powinniśmy stosować wybiórczo. Używanie wielu zagnieżdżonych pętli często powoduje powstawanie kodu, który jest trudny w zrozumieniu, utrzymaniu, oraz testowaniu.

Ponadto, zagnieżdżone pętle mogą też powodować wolne działanie programu – zauważmy, że na każdy obieg pętli zewnętrznej wykonywane są wszystkie iteracje pętli wewnętrznej – liczba iteracji rośnie bardzo szybko, im więcej mamy pętli zagnieżdżonych.

Użycie break i continue w pętlach zagnieżdżonych

Wiemy już, że instrukcje break oraz continue powodują, odpowiednio, przerwanie działania pętli oraz przerwanie aktualnej iteracji.

Instrukcje break oraz continue odnoszą się tylko do pętli, w których zostały użyte – ma to znaczenie w przypadku pętli zagnieżdżonych, ponieważ użycie break lub continue w pętli zagnieżdżonej nie spowoduje, że pętla zewnętrzna przerwie działanie.

Spójrzmy na poniższy przykład – jaki będzie wynik jego działania?

Nazwa pliku: ZagniezdzonaPetlaBreak.java
public class ZagniezdzonaPetlaBreak {
  public static void main(String[] args) {
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 5; j++) {
        System.out.print(j + ", ");

        if (j == 1) break;
      }

      System.out.println();
    }
  }
}

Na ekranie zobaczymy:

0, 1, 0, 1, 0, 1,

Dlaczego instrukcja break nie przerwała obu pętli? Jest to normalne działanie – zarówno instrukcje break jak i continue, odnoszą się zawsze do tej pętli, w której zostały użyte. Aby przerwać główną pętlę, musielibyśmy użyć break "o poziom wyżej":

Nazwa pliku: ZagniezdzonaPetlaBreak2.java
public class ZagniezdzonaPetlaBreak2 {
  public static void main(String[] args) {
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 5; j++) {
        System.out.print(j + ", ");

        if (j == 1) break;
      }

      System.out.println();
      break;
    }
  }
}

Wynik:

0, 1,

A co w przypadku, gdybyśmy chcieli spowodować w zagnieżdżonej pętli, by przerwana została główna pętla (bądź spowodować przejście do kolejnej iteracji za pomocą continue)? Możemy w tym celu skorzystać z opcjonalnych etykiet, które możemy przyporządkować pętlom – spójrzmy na przykład:

Nazwa pliku: ZagniezdzonaPetlaBreakEtykieta.java
public class ZagniezdzonaPetlaBreakEtykieta {
  public static void main(String[] args) {
    glowna_petla: for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 5; j++) {
        System.out.print(j + ", ");

        if (j == 1) break glowna_petla;
      }
    }
  }
}

W zaznaczonej linii, przed słowem kluczowym for umieściliśmy etykietę, po której następuje dwukropek. Następnie, w pętli zagnieżdżonej po instrukcji break podaliśmy nazwę etykiety. Wynik działania jest następujący:

0, 1,

Instrukcja break z użyciem etykiety spowodowała przerwanie głównej, a nie zagnieżdżonej, pętli.

Etykiety można stosować:

  • z każdym rodzajem pętli,
  • na dowolnym poziomie zagnieżdżenia,
  • z instrukcjami break oraz continue.
Etykiety i przerywanie pętli "wyższego poziomu" z zagnieżdżonych pętli są bardzo rzadko stosowane – po prostu rzadko zdarzają się przypadki, które takiej funkcjonalności wymagają.

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.