19 lipca 2008

SCJP - Pętle

Rozdział piąty Książki – patrz artykuł "Przygotowania do SCJP czas zacząć" – wydawał się być bardzo długi a przez to straszny, ale nie jest źle. Idzie dosyć szybko, a to pewnie z tego powodu, że poruszane w nim tematy nie są przesadnie skomplikowane. Dziś będzie właśnie jeden z nich, tj. pętle. Pętle ‘while’ i ‘do while’ nie zaskakują zupełnie niczym, więc nic o nich nie będę pisał. Przejdę od razu do pętli ‘for’, oraz instrukcji ‘break’ i ‘continue’.

Pętla ‘for’ występuje w dwu odmianach: tradycyjnej, ogólnego przeznaczenia i nowej – obecnej od Javy 1.5 – służącej do iteracji po elementach kolekcji lub tablicy. O drugim wariancie będę pisał w osobnym artykule, kiedyś w przyszłości. Dziś zobaczmy tylko prosty przykład, jak taka pętla wygląda. Aby więc wykonać jakąś akcję dla kolejnych elementów tablicy, w tym przypadku dla tablicy liczb typu ‘int’, napiszemy

for (int i : new int[] { 1, 3, 5, 7, 11 }) {
System.out.print(i + " ");
}

Generalnie, przed dwukropkiem deklarujemy zmienną, do której będą przypisywane kolejne wartości z kolekcji, a po dwukropku umieszczamy dowolne wyrażenie, którego wartością jest kolekcja lub tablica. W naszym wypadku jest to instrukcja tworząca nową tablicę, ale może to być także wywołanie metody czy zmienna referencyjna odnosząca się do obiektu pewnej kolekcji, np.

public static void main(String[] args) {
List<Integer> someList = new LinkedList<Integer>();

someList.add(1);
someList.add(2);
someList.add(3);

for (int i : someList) {
System.out.print(i + " ");
}
}

Tradycyjna pętla ‘for’ składa się z trzech, oddzielonych średnikami sekcji w części sterującej pętli, oraz oczywiście z kodu umieszczonego w tejże pętli. Przyjęło się mówić, że pierwsza sekcja służy do deklaracji zmiennych, druga to warunek pętli, a więc wyrażenie o typie ‘boolean’ a trzecia to instrukcja inkrementacji zmiennych. Co prawda tak się tego zazwyczaj używa, ale trzeba wiedzieć, że pierwsza i trzecia sekcja może zawierać dowolną instrukcję. Sekcja druga to dowolne wyrażenie typu ‘boolean’. Dodatkowo, każda z sekcji może być zupełnie pusta. Przykładowo, poprawną jest pętla

for (System.out.print("sekcja pierwsza") ; ; System.out.print("sekcja trzecia")) {
break;
}

Jeśli druga sekcja jest pusta, to pętla zachowuje się tak, jakby był tam umieszczony literał ‘true’, a więc jest to pętla nieskończona, a właściwie to byłaby to pętla nieskończona, gdyby nie instrukcja ‘break’, umieszczona w ciele pętli. Sekcja pierwsza jest to kod, który uruchamiany jest na samym początku, zawsze dokładnie raz, niezależnie od wartości sekcji drugiej. Przykładowo, uruchomienie programu

public static void main(String[] args) {
int x = 1, y = x;

for (System.out.print("sekcja pierwsza") ; x != y ;
System.out.print("sekcja trzecia")) {

System.out.print("ciało pętli");
}
}

spowoduje wyświetlenie napisu

sekcja pierwsza

Po wykonaniu sekcji pierwszej, przeznaczonej na inicjalizację zmiennych sterujących pętli, obliczana jest wartość wyrażenia z sekcji drugiej; jeśli jest to ‘true’, to wykonuje się ciało pętli. Na końcu wykonywany jest kod z sekcji trzeciej nagłówka pętli, przeznaczonej na kod inkrementujący wartości zmiennych sterujących.

Instrukcja ‘break’ służy do natychmiastowego przerwania wykonania pętli – sterowanie zostaje wówczas przeniesione do pierwszej instrukcji za pętlą. Efektem uruchomienia programu

public static void main(String[] args) {
int i = 0;

for (System.out.println("sekcja pierwsza") ; condition() ;
System.out.println("sekcja trzecia")) {

System.out.println("ciało pętli");

if(i++ > 0)
break;
}

System.out.println("po pętli");
}

public static boolean condition() {
System.out.println("sekcja druga (true)");

return true;
}

jest wyświetlenie sekwencji

sekcja pierwsza
sekcja druga (true)
ciało pętli
sekcja trzecia
sekcja druga (true)
ciało pętli
po pętli

Instrukcja ‘continue’ przerywa bieżące wykonanie pętli, a więc tylko wykonanie ciała pętli w bieżącej iteracji. Zwróćmy uwagę, że instrukcja ‘continue’ nie powoduje zaniechania wykonania trzeciej sekcji nagłówka pętli. Efektem uruchomienia programu

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i % 2 == 1)
continue;

System.out.println(i + " jest liczbą parzystą");
}
}

jest wyświetlenie sekwencji

0 jest liczbą parzystą
2 jest liczbą parzystą
4 jest liczbą parzystą
6 jest liczbą parzystą
8 jest liczbą parzystą

Instrukcje ‘break’ i ‘continue’ odnoszą się domyślnie do pętli, bezpośrednio w której się znajdują, ale możliwe jest wskazanie, poprzez etykietę, pętli bardziej zewnętrznej. Przykładowo, uruchomienie programu

public static void main(String[] args) {
outerLoop: for (int j = 0;; j += 100) {
for (int i = 0; i < 5; i++) {
if ((i + j) % 2 == 1)
continue;

if (j > 100)
break outerLoop;

System.out.println(i + j + " jest liczbą parzystą");
}
}
}

skutkuje wyświetleniem sekwencji

0 jest liczbą parzystą
2 jest liczbą parzystą
4 jest liczbą parzystą
100 jest liczbą parzystą
102 jest liczbą parzystą
104 jest liczbą parzystą

1 komentarz:

Anonimowy pisze...

Ponieważ w artykule wykorzystano przykład operatora modulo dla badania parzystości od razu przypomniał mi się pierwszy puzzle z książki "Java Puzzlers" (Joshua Bloch)

public static boolean isOdd(int i) {
return i % 2 == 1;
}

Czy ta metoda zawsze zwróci poprawną wartość dla liczby nieparzystej??

Otóż nie! Dla liczby ujemnych nie będzie działać poprawnie ponieważ w skrócie znak wyniku operatora modulo zależy od znaku lewego argumentu.

cytując za książką
This is a consequence of the definition of Java's remainder operator (%). It is defined to satisfy the following identity for all int values a and all nonzero int values b:

(a / b) * b + (a % b) == a

In other words, if you divide a by b, multiply the result by b, and add the remainder, you are back where you started [JLS 15.17.3]. This identity makes perfect sense, but in combination with Java's truncating integer division operator [JLS 15.17.2], it implies that when the remainder operation returns a nonzero result, it has the same sign as its left operand.