Rozdział 4 - Instrukcje warunkowe - Bloki kodu i zakresy zmiennych

Ważnym konceptem w programowaniu jest zakres widoczności i "życia" zmiennych i innych obiektów.

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

Nazwa pliku: UzyciePrzedDefinicja.java
public class UzyciePrzedDefinicja {
  public static void main(String[] args) {
    System.out.println("Liczba = " + liczba);

    int liczba = 5;
  }
}

Powyższy program w ogóle się nie skompiluje – kompilator zgłosi błąd, ponieważ próbowaliśmy użyć zmiennej liczba zanim ją zdefiniowaliśmy w metodzie main:

UzyciePrzedDefinicja.java:3: error: cannot find symbol System.out.println("Liczba = " + liczba); ^ symbol: variable liczba location: class UzyciePrzedDefinicja 1 error

Zmienna liczba istnieje dopiero od linii, w której zostanie zdefiniowana.

Spójrzmy na kolejny przykład – jaki będzie wynik Twoim zdaniem?

Nazwa pliku: ZmiennaWBlokuIf.java
public class ZmiennaWBlokuIf {
  public static void main(String[] args) {
    int licznik = 9;
    int mianownik = 3;

    if (mianownik != 0) {
      double wynik = licznik / mianownik;
    }

    System.out.println("Wynik = " + wynik);
  }
}

Program ponownie się nie skompiluje:

ZmiennaWBlokuIf.java:10: error: cannot find symbol System.out.println("Wynik = " + wynik); ^ symbol: variable wynik location: class ZmiennaWBlokuIf 1 error

Kompilator protestuje, ponieważ nie wie czym jest "wynik".

Powodem jest fakt, że zmienną wynik utworzyliśmy w bloku instrukcji if. Zakres "życia" zmiennej wynik to jedynie blok instrukcji if – poza nim, zmienna ta przestaje istnieć i jest niedostępna w dalszej części kodu.

Spójrzmy na jeszcze jeden przykład:

Nazwa pliku: ZakresZmiennych.java
Zakres zmiennych w blokach

Kolorami zaznaczone są fragmenty kodu, w których dostępne są poszczególne zmienne:

  • Zmienna trzeciaLiczba dostępna jest jedynie w zagnieżdżonym bloku instrukcji if, ponieważ została w nim zdefiniowana – poza tym blokiem, zmienna trzeciaLiczba nie istnieje.
  • Z kolei zmienna drugaLiczba dostępna jest zarówno w "głównym" bloku if, jak i w drugim, zagnieżdzonym w nim, bloku if. Zagnieżdżone bloki kodu mają dostęp do zmiennych zdefiniowanych wcześniej w zewnętrznych blokach, ale nie na odwrót.
  • Zmienna liczba dostępna jest w całej metodzie main, od jej początku, aż do końca metody, ponieważ została zdefiniowana na samym jej początku.

W sekcji else głównej instrukcji warunkowej mamy dostęp jedynie do zmiennej liczba, ponieważ została zdefiniowana wcześniej w zewnętrznym bloku (którym jest ciało metody main). W sekcji else ani zmienna drugaLiczba, ani trzeciaLiczba, nie są znane, bo jeżeli doszło do wykonania kodu w sekcji else, to zmienne te nie zostały nigdy utworzone.

Wrócimy jeszcze do zakresu widoczności oraz czasu "życia" zmiennych w kolejnych rozdziałach.

Bloki kodu w instrukcjach warunkowych

Na początku rozdziału o instrukcjach warunkowych dowiedzieliśmy się, że klamry przed i po instrukcjach przyporządkowanych do danej sekcji instrukcji warunkowej nie są wymagane, jeżeli do wykonania jest tylko jedna instrukcja – poniższy kod:

int x = 5;

if (x > 0) {
  System.out.println("x jest wieksze od 0.");
} else if (x < 0) {
  System.out.println("x jest mniejsze od 0.");
} else {
  System.out.println("x jest rowne 0.");
}

moglibyśmy zapisać także jako:

int x = 5;

if (x > 0)
  System.out.println("x jest wieksze od 0.");
else if (x < 0)
  System.out.println("x jest mniejsze od 0.");
else
  System.out.println("x jest rowne 0.");

Wynika to z faktu, że w powyższym przykładzie sekcje warunkowe mają po jednej instrukcji do wykonania. Powinniśmy jednak zawsze stosować klamry { }, nawet wtedy, gdy instrukcja warunkowa ma przypisaną tylko jedną instrukcję do wykonania.

Zobaczmy w poniższym przykładzie-zagadce co może się stać, jeżeli zapomnimy otoczyć więcej niż jedną instrukcję w klamry:

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

public class IfBezBloku {
  public static void main(String[] args) {
    int licznik, mianownik;

    System.out.println("Podaj licznik:");
    licznik = getInt();

    System.out.println("Podaj mianownik:");
    mianownik = getInt();

    double wynik;

    if (mianownik != 0)
      wynik = licznik / mianownik;
      System.out.println("Wynik: " + wynik);
}
  public static int getInt() {
    return new Scanner(System.in).nextInt();
  }
}

Jaki będzie wynik działania powyższego programu?

Program nawet się nie skompiluje! Zobaczymy komunikat, który widzieliśmy już w jednym z przykładów w poprzednim rozdziale, gdy poznawaliśmy zmienne:

IfBezBloku.java:18: error: variable wynik might not have been initialized System.out.println("Wynik: " + wynik); ^ 1 error

Dlaczego zobaczyliśmy powyższy błąd? Nie otoczyliśmy obu linijek pod instrukcją if klamrami, więc do instrukcji if przyporządkowana jest tylko jedna instrukcja – nadanie wartości zmiennej wynik.

Jak wiemy z poprzedniego rozdziału, każdej zmiennej, zanim zostanie użyta, musi zostać nadana jakaś wartość.

W powyższym kodzie nadajemy zmiennej wynik wartość tylko w przypadku, gdy zmienna mianownik ma wartość różną od zera, więc istnieje taka ścieżka wykonania programu, w której zmiennej wynik nie zostałaby przypisana żadna wartość przed jej wypisaniem w linijce:

System.out.println("Wynik: " + wynik);

Kompilator Java jest na tyle "mądry", że jest w stanie wychwycić takie sytuacje i poinformować nas o nich jeszcze na etapie kompilacji.

Gdybyśmy nie zapomnieli o objęciu obu instrukcji (nadania wartości i jej wypisania) w blok za pomocą nawiasów klamrowych { }, to próba wypisania wartości odbyła by się tylko po uprzednim nadaniu jej wartości, dzięki czemu kod byłby poprawny, a kompilator by nie protestował.

Dodaj komentarz

Twój adres email nie zostanie opublikowany.