5. Zasięg identyfikatorów. Zmienne lokalne. Czas życia danych. Specyfikatory dostępu
Gdy definiujemy jakąś klasę niebagatelną kwestią okazuje się pytanie o
możliwości działania na określonych zmiennych. Zwykle sprawia to początkującym
w Javie programistom wiele trudności. Wszystkie instrukcje (oprócz deklaracji) można umieszczać wyłącznie w metodach klasy
(jest od tego wyjątek: blok statyczny, ale w tym momencie go pominiemy) Zasięgiem identyfikatora jest fragment programu, w którym może on
być używany (w którym identyfikator jest rozpoznawany przez kompilator).
W każdej metodzie klasy możemy zawsze odwołać się do identyfikatorów składowych klasy (pól i metod), niezależnie od tego w którym miejscu klasy występuje deklaracja tych pól i metod.
W inicjatorach zawartych w deklaracjach pól klasy możemy odwoływac się do wcześniej inicjowanych (por. punkt o inicjacji) identyfikatorów pól klasy Przypomnijmy: ciało metody czyli jej kod ujmowany jest w nawiasy klamrowe. Ujęty w nawiasy klamrowe kod nazywa się również blokiem
W każdej metodzie możemy deklarować nowe zmienne (lub stałe). Zasięg ich identyfikatorów jest lokalny - rozciąga się od miejsca deklaracji do końca metody (końca bloku stanowiącego ciało metody) w której zostały zadeklarowane. Mówimy o nich zmienne (stałe) lokalne. Dotyczy to również parametrów (których deklaracje występują w nagłówku
metody). Tak naprawdę, parametry są zmiennymi lokalnymi o zasięgu od miejsca
deklaracji do końca bloku obejmującego ciało metody. class A { int a; void metoda1() { int b; ... } void metoda2() { int c; ... } } to w metodzie metoda1 możemy odwoływać się do zmiennej a, zmiennej b, oraz
metody metoda2(), a w metodzie metoda2() możemy odwoływac się do zmiennej
a, zmiennej c i metody metoda1. Blędem natomiast będzie próba odwołania się
z metody1 do zmiennej c i z metody2 do zmiennej b. class A { int a; void metoda() { int a = 0; // przesłonięcie identyfikatora pola a = a + 10; // dotyczy zmiennej lokalnej; this.a++; // dotyczy pola } }
Tutaj w metodzie metoda() wprowadziliśmy zmiennę lokalną o tej samej nazwie
co pole klasy (przesłonięcie identyfikatora). Samo odwołanie a
będzie dotyczyć tej zmiennej lokalnej. Jak pamiętamy, przy takim przesłonięciu
możemy odwolać się do pola używając zmiennej this. W Javie nie wolno przesłaniać zmiennych lokalnych w blokach wewnętrznych.
Np. konstrukcja: Zmienne lokalne muszą mieć na pewno nadane wartości. W przeciwnym razie wystąpi błąd w kompilacji, związany z naruszeniem tzw. "definite assignment rule"
Np. poniższy program: import javax.swing.*; public class DefAssgnm { public static void main(String[] args) { int len; String s = JOptionPane.showInputDialog("Napis?"); if (s != null) len = s.length(); System.out.println("Długosc napisu : " + len); } nie skompiluje się poprawnie, a kompilator powiadomi nas, że: DefAssgnm.java:9: variable len might not have been initialized
System.out.println("Długosc napisu : " + len); ^ 1 error
ponieważ zmienna len jest lokalna w metodzie main i w programie uzyska wartość
tylko warunkowo (gdy s != null). Zatem może się zdarzyć, że nie będzia miała
przypisanej żadnej wartości. import javax.swing.*; public class DefAssgnm { public static void main(String[] args) { int len; String s = JOptionPane.showInputDialog("Napis?"); if (s != null) len = s.length(); else len = -1; System.out.println("Długosc napisu : " + len); } }
Z zasięgiem identyfikatorów wiąże się w pewnym sensie czas życia danych, ale nie są to pojęcia tożsame. Czas życia danych to okres od momentu wydzielenia pamięci dla ich przechowywania do momentu zwolnienia tej pamięci.
Zmienne lokalne są powoływane do życia w momencie deklaracji (automatyczne
wydzielenie pamięci na stosie) i likwidowane przy wyjściu sterowania z bloku,
w którym zostału zadeklarowane (automatyczne zwolnienie pamięci). Dotyczy
to również tych zmiennych, które są referencjami do obiektów. Wartości zmiennych lokalnych są tracone po wyjściu sterowania z bloku - np. zakończeniu działania metody
Ta oczywista prawda niekiedy jest niedostrzegana i niektórzy starają się
np za pomocą zmiennych lokalnych zliczać liczbę wywołań jakiejś metody (usiłują
wymyślić sposob na to, a przecież to niemożliwe). public class Count { private int counter; public void increase() { counter++; } public void show() { System.out.println(counter); } } class Test { public static void main(String[] args) { Count c = new Count(); c.increase(); c.increase(); c.increase(); c.show(); } }
Różnicę pomiędzy życiem zmiennych lokalnych i obiektów dobrze ilustruje poniższy przykład: class Pies { String s; void nowyPies() { String pies = new String("pies główny"); s = pies; } void jakieMamyPsy() { System.out.println("Jest " + s); String pies2 = pobierzInnegoPsa(); System.out.println("Jest też " + pies2); } String pobierzInnegoPsa() { String p = new String("inny pies"); return p; } } class Test { public static void main(String[] args) { Pies p = new Pies(); p.nowyPies(); p.jakieMamyPsy(); } } Jest pies główny
Jest też inny pies Program wyprowadzi następującą informację, gdyż
A jak wygląda sytuacja z dostępem do pól i metod danej klasy w innej klasie?
Pewne powody stosowania specyfikatorów dostępu już omawialiśmy (zabronienie
bezpośredniego dostępu do "wnętrza" obiektów, udostępnienie użytkownikom
klasy okreslonego zestawu metod publicznych). Więcej o specyfikatorach dostępu
dowiemy się w następnym semestrze, teraz zauwazmy tylko, że stanowią one
prawdziwe restrykcje. import javax.swing.*; public class Para { private int a; private int b; public String nazwa; public Para(int x, int y) { a = x; b = y; } private String makeString() { return nazwa + " " + a + " i " + b; } public void show() { JOptionPane.showMessageDialog(null, makeString()); // 6 } } class Test { public static void main(String[] args) { Para p = new Para(17,20); p.nazwa = "Para liczb"; // 1 p.a = 1; // 3 System.out.println(p.makeString()); // 4 p.show(); // 5 } } Spowoduje błędy w kompilacji: Para.java:30: a has private access in Para
p.a = 1; // 3 ^ Para.java:31: makeString() has private access in Para System.out.println(p.makeString()); // 4 ^ 2 errors przy czym:
Mamy też w Javie pojęcie klas publicznych i pakietowych. Klasa pakietowa jest
dostępna tylko z klas pakietu. Klasa publiczna jest dostępna zewsząd (z innych
pakietów). W pliku źródłowym może być zdefiniowana tylko jedna klasa publiczna (ale
nie musi być). Jeśli w pliku źródłowym jest zdefiniowana klasa publiczna,
to plik musi mieć taką samą nazwę jak ta klasa - z dokładnością do wielkości
liter
Definicji klas pakietowych (bez specyfikatora public) może być w jednym pliku wiele.
|