Zasięg deklaracji i czas istnienia zmiennych
Jak dotąd wszystkie zmienne deklarowaliśmy na początku metody main(). Jednak Java umożliwia ich deklarowanie wewnątrz dowolnego bloku kodu. Jak wyjaśniłem w rozdziale 1., blok taki rozpoczyna się otwierającym nawiasem klamrowym i kończy zamykającym nawiasem klamrowym. Blok kodu definiuje jednocześnie zasięg deklaracji zmiennych. Rozpoczynając nowy blok, tworzymy nowy zasięg. Zasięg określa, które obiekty są widoczne dla innych części programu. Decyduje równocześnie o czasie
istnienia obiektów.
W niektórych innych językach programowania istnieją dwie ogólne kategorie zasięgu: globalny i lokalny. Oba istnieją również w języku Java, ale nie stanowią w nim najlepszego sposobu kategoryzacji zasięgów. W Javie najistotniejsze są zasięgi definiowane przez klasy i metody. Omówienie zasięgu klasy (i zmiennych deklarowanych wewnątrz) pozostawimy na razie do momentu, kiedy bliżej zajmiemy się klasami. Tutaj zajmiemy się jedynie zasięgiem definiowanym przez metodę.
Zasięg definiowany przez metodę rozpoczyna się wraz z nawiasem klamrowym otwierającym jej ciało. Jednak należy pamiętać, że jeżeli metoda ma parametry, to również one należą do zasięgu metody. Obowiązuje ogólna zasada, że zmienne zadeklarowane w danym zasięgu nie są widoczne (i tym samym dostępne) dla kodu zdefiniowanego na zewnątrz tego zasięgu. Zatem deklarując zmienną wewnątrz zasięgu, czynisz ją lokalną dla tego zasięgu i chronisz ją przed nieuprawnionym dostępem
i (lub) modyfikacją. Zasady rządzące zasięgami stanowią fundament hermetyzacji.
Zasięgi można zagnieżdżać. Na przykład za każdym razem, gdy tworzysz blok kodu, równocześnie zagnieżdżasz nowy zasięg. W tym momencie zasięg zewnętrzny obejmuje zasięg wewnętrzny. Oznacza to, że obiekty zadeklarowane w zasięgu zewnętrznym są widoczne dla kodu w zasięgu wewnętrznym. W kierunku przeciwnym nie jest to prawdą. Obiekty zadeklarowane w zasięgu wewnętrznym nie są widoczne na zewnątrz niego.
Aby zrozumieć efekt zagnieżdżania zasięgów, rozważmy program przedstawiony na listingu 2.8.
Listing 2.8. ScopeDemo.java
class ScopeDemo {
public static void main(String args[]) {
int x; // widoczna w całym kodzie metody main
x = 10;
if(x == 10) { // początek nowego zasięgu
int y = 20; // widoczna tylko w tym bloku
// tutaj widoczne x i y
System.out.println("x i y: " + x + " " + y);
x = y * 2;
}
// y = 100; // błąd! y nie jest tu widoczne Tutaj y poza zasięgiem.
// x jest tu nadal widoczne
System.out.println("x jest " + x);
}
}
Jak informują o tym komentarze, zmienna x została zadeklarowana na początku zasięgu metody main() i jest dostępna dla następującego po niej kodu metody main(). Wewnątrz bloku instrukcji if została zadeklarowana zmienna y. Ponieważ blok definiuje zasięg, zmienna y jest widoczna tylko wewnątrz bloku. Dlatego też na zewnątrz bloku instrukcja y = 100; została umieszczona w komentarzu. Jeśli usuniesz poprzedzający ją znak komentarza, podczas kompilacji wystąpi błąd, ponieważ zmienna y nie jest widoczna na zewnątrz swojego bloku. Natomiast zmienna x może być używana wewnątrz bloku x, ponieważ kod wewnątrz bloku (czyli zagnieżdżonego zasięgu) ma dostęp do zmiennych zadeklarowanych w zasięgu obejmującym.
Wewnątrz bloku możesz definiować zmienne w dowolnym miejscu, ale są one dostępne dopiero dla kodu następującego po deklaracji. Zatem jeśli zadeklarujesz zmienną na samym początku metody, będzie ona dostępna w całym kodzie metody. Przeciwnie, jeśli zadeklarujesz zmienną na końcu metody, okaże się ona bezużyteczna, gdyż nie będzie dostępna dla żadnego kodu.
Z zasięgiem i zmiennymi wiąże się jeszcze jedna ważna zasada, którą powinieneś zapamiętać: zmienne są tworzone wraz ze swoim zasięgiem i usuwane, gdy wykonanie programu osiągnie koniec zasięgu. Oznacza to, że gdy program opuści zasięg zmiennej, nie przechowuje już ona swojej wartości. Z tego powodu zmienne nie zachowują swoich wartości pomiędzy kolejnymi wywołaniami tej samej metody. Również po opuszczeniu bloku kodu wartość zadeklarowanych w nim zmiennych zostaje
utracona. Czas istnienia zmiennej jest więc ściśle związany z jej zasięgiem.
Jeśli deklaracja zmiennej zawiera inicjalizator, to zmienna otrzyma wartość początkową za każdym razem, gdy sterowanie trafia do bloku, w którym zmienna ta została zdefiniowana. Rozważmy przykład programu przedstawiony na listingu 2.9.
Listing 2.9. VarInitDemo.java
class VarInitDemo {
public static void main(String args[]) {
int x;
for(x = 0; x < 3; x++) {
int y = -1; // y zostaje zainicjalizowana z każdym rozpoczęciem bloku
System.out.println("y wynosi: " + y); // dlatego zawsze wyświetla wartość -1
y = 100;
System.out.println("a teraz: " + y);
}
}
}
Wykonanie tego programu spowoduje wyświetlenie poniższych informacji.
y wynosi: -1
a teraz: 100
y wynosi: -1
a teraz: 100
y wynosi: -1
a teraz: 100
Jak zauważyłeś, zmienna y otrzymuje wartość początkową –1 za każdym razem, gdy wykonywana jest pętla for. Mimo że później przypisana zostaje jej wartość 100, zostaje ona utracona. Zagnieżdżanie bloków ma jeden aspekt, który może Cię zaskoczyć: żadna zmienna bloku wewnętrznego nie może mieć takiej samej nazwy jak zmienna zadeklarowana w bloku obejmującym. Dlatego też program przedstawiony na listingu 2.10 i zawierający deklaracje dwóch zmiennych o tej samej nazwie nie zostanie skompilowany.
Listing 2.10. NestVar.java
Ten program próbuje zadeklarować
w wewnętrznym zasięgu zmienną
o nazwie, która jest już zdefiniowana
w zasięgu zewnętrznym.
*** Ten program nie zostanie skompilowany. ***
*/
class NestVar {
public static void main(String args[]) {
int count;
for(count = 0; count < 10; count = count+1) {
System.out.println("Wartość zmiennej count: " + count);
int count; // niedozwolone!!! Nie można zadeklarować count, zmienna o tej nazwie już istnieje!
for(count = 0; count < 2; count++)
System.out.println("Ten program zawiera błąd!");
}
}
}
Jeśli znasz język C/C++, to wiesz, że nie ma w nim żadnych ograniczeń nazw zmiennych deklarowanych w zagnieżdżanych zasięgach. Dlatego też w C/C++ deklaracja zmiennej count w bloku zewnętrznej pętli for byłaby poprawna i przesłoniłaby zmienną zewnętrzną. Projektanci Javy zdecydowali jednak, że przesłanianie nazw może być przyczyną błędów programistycznych i wobec tego zabronili go.
Java. Przewodnik dla początkujących. Wydanie VI, Autor: Herbert Schildt, Wydawnictwo: Helion