9 marca 2008

SCJP - Modyfikatory widoczności w deklaracji metod i zmiennych

Modyfikatory widoczności w deklaracji metod i zmiennych, czyli streszczenie sekcji "Access Modifiers" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć".

Metody i zmienne w deklaracji klas mogą mieć każdy z czterech zakresów widoczności. Trzy zakresy widoczności odpowiadają trzem słowom kluczowym: public, protected i private a czwarty to zakres domyślny, który obowiązuje, jeśli nie określimy żadnego z powyższych modyfikatorów.

Przyszła pora na to, by zastanowić się dokładniej nad tym, co to właściwie oznacza "widoczność". Otóż ma ona dwa aspekty. Po pierwsze interesuje nas, czy da się odwołać do metody lub zmiennej klasy za pośrednictwem jej instancji i po drugie, czy da się do nich odwołać z kodu klasy pochodnej, a więc czy podlegają dziedziczeniu. Poniższy kawałek kodu ilustruje to zagadnienie.

public class Parent {
protected int x = 5;
}

public class Child extends Parent {
int parentInstanceAccess() {
Parent parent = new Parent();

// poniższa instrukcja nie zawsze jest poprawna
return parent.x;
}

int inheritanceAccess() {
return x;
}
}

Tak jak zasygnalizowałem w komentarzu, powyższy kod nie zawsze jest poprawny, a to czy jest zależy od tego czy obie klasy znajdują się w tym samym pakiecie. Więcej na ten temat za parę zdań, póki co ważne jest żeby zrozumieć dwojakość pojęcia widoczności.

Zmienne i metody oznaczone jako private są widoczne tylko i wyłącznie w klasie, która je definiuje i są widoczne zarówno przy dostępie "lokalnym" jak i dostępie za pośrednictwem innej instancji tej klasy. Zmienne i metody prywatne nie są dziedziczone. Poniżej przykład poprawnego kodu ilustrującego zagadnienie.

public class SomeClass {
private int x = 5;

int instanceAccess() {
SomeClass instance = new SomeClass();

return instance.x;
}

int thisAccess() {
return x;
}
}

Jeszcze słówko komentarza, aby ułatwić zrozumienie zagadnienia. Instrukcja ‘return x’ to de facto instrukcja ‘return this.x’ gdzie ‘this’ to nic innego jak wskaźnik na instancję naszej klasy dla której wywołano metodę. Instrukcja ‘return instance.x’ jest więc z punktu widzenia widoczności tym samym co ‘return this.x’ - obie te instrukcje odwołują się do tej samej zmiennej poprzez instancję tej samej klasy i w tej samej klasie.

Zmienne i metody oznaczone jako public są widoczne mówiąc najogólniej wszędzie. Oczywiście trzeba jeszcze rozważyć, czy klasa, która definiuje taką metodę lub zmienną też jest widoczna - jeśli nie, to naturalnie metoda czy zmienna też nie jest. Metody i zmienne publiczne są dziedziczone i nie ma tu żadnych udziwnień.

Zmienne i metody nieoznaczone żadnym modyfikatorem widoczności, tj. o widoczności domyślnej są widoczne tylko w klasach z tego samego pakietu, co klasa zawierająca deklaracje. Takie metody i zmienne są również dziedziczone tylko przez klasy z tego samego pakietu.

Z modyfikatorem protected jest najciekawiej – tj. najtrudniej, – więc zostawiłem go sobie na koniec. Modyfikator ten jest rozszerzeniem domyślnego zakresu widoczności w tym sensie, że również powoduje ograniczenie widoczności do pakietu, ale ograniczenie to nie dotyczy klas, które z klasy deklarującej metodę lub zmienną protected dziedziczą. Jeśli więc klasa A deklaruje metodę o widoczności protected to metoda ta jest dostępna w klasie B tylko i wyłącznie wtedy, gdy klasa B znajduje się w tym samym pakiecie albo, gdy dziedziczy (wprost lub przechodnio) z klasy A. Zakończmy przykładem obrazującym to zagadnienie.

public class Parent {
protected int x = 5;
}

public class Child extends Parent {
int parentInstanceAccess() {
Parent parent = new Parent();

// działa tylko, jeśli klasy są w tym samym pakiecie
return parent.x;
}

int thisInstanceAccess() {
Child child = new Child();

// to zawsze jest ok
return child.x;
}

int inheritanceAccess() {

// zawsze ok
return x;
}
}

2 komentarze:

Mariusz Lipiński pisze...

I jeszcze małe uzupełnienie.

Zerknijmy na klasy Parent i Child pokazane na końcu artykułu. Załóżmy, że klasa Parent jest w pakiecie A a klasa Child w pakiecie B. Pomińmy fakt, że metoda parentInstanceAccess() się nie skąpiluje, wyobraźmy sobie, że jej tam nie ma. Pytanie brzmi - czy klasa:

class Other {
int test() {
Child child = new Child();

return child.x;
}
}

się skąpiluje?

Odpowiedź brzmi - to zależy, czy jest w pakiecie A czy B!

Anonimowy pisze...

Dzięki, przydało się.

Na marginesie, chyba "kompilacja" nie "kąpilacja"?