Klasy wewnętrzne, anonimowe klasy wewnętrzne
Prosty przykład klas wewnętrznych
Dozwolone jest umieszczanie definicji klasy wewnątrz innej klasy.
Poniższy przykład ilustruje składnię klas wewnętrzych i bardzo prosty ich wykorzystania.
Klasy wewnętrzne umożliwiają logiczne grupownie klas i kontrolowanie dostępu do klas. Nie jest to tożsame z budowaniem klas przez kompozycję, ani dziedzidzenia z wielu klas.
W programie, w klasie P30Wewn umieszczone są dwie klasy Pierwsza i Druga. Metoda funkcjaD() buduje i zwraca obiekt klasy Druga. I w taki zwykle sposób jest realizowane tworzenie obiektów klas wewnętrznych w klasie zewnętrznej - tzn. budowana jest metoda zwracająca referencję do obiektu klasy wewnętrznej.
Pozyskiwanie referencji klasy wewnętrznej poza klasą ją otaczającą wymaga podania nazwy klasy otaczającej, potem nazwy klasy wewnętrznej. Przy czym taka składnia jest niezbędna wtedy, gdy referencja do klasy wewnętrznej jest tworzona poza klasą ją otaczającą.
P30Wewn.java
Klasy wewnętrzne i rzutowanie
Klasy wewnętrzne są jeszcze jedną metodą ukrywania implementacji. Klasy wewnętrzne mogą być prywatnymi klasami (przeciwieństwie do zwykłych, które mogą być publiczne lub przyjazne).
Przydatność klas wewnętrznych do ukrywania implemetacji jest widoczna szczególnie wtedy, gdy będziemy rzutować w górę do klasy bazowej lub do interfejsu. Można tak napisać program, by klasa wewnętrzna implementująca interfejs była zupełnie niewidoczna, a jedyne co można dostać, to referencje do klasy bazowej, zwykłej, abstrakcyjnej lub interfejsu.
Przeanalizujemy zadanie składające się z dwóch interfesów: InterPierwszy, InterDrugi i poniższego programu.
Przykładzie tym, metoda main() jest zamieszczona w klasie Testujaca, która nie jest klasą niepubliczną. Wykonanie programu wymaga wywołnia postaci java Testujaca.
P31Wewn.java
W powyższym programie
Klasa o nazwie ZDrugiego, jest to klasą prywatną, jest dotępna jedynie z klasy P31Wewn. Klasa ZPierwszego jest klasą dostępną dla klas wywiedzionych z P31Wewn i klas pochodzących z tego samego pakietu.
Klasa ZDrugiego jest wewnętrzną klasą prywatną i jest dostępna tylko w klasie P31Wewn (patrz komentarz).
Można utworzyć obiekty będące interfejsami (dokładniej mające klasę interfejsu) i wykorzystywać implementację tych interfesów zamieszczoną w klasach wewnętrznych. Nie można rzutować w dół.
Nazwa klasy wewnętrznej implementującej interfejs może być nieznana. Jest to skuteczny sposób ukrywania implementacji.
Stosowanie klas wenętrznych dziedziczących z interfejsów wymusza programowanie niezależne od konkretnego typu i wykorzysyujące ukrytą implementację.
Klasy wewnętrzne mogą być prywatne. Klasy zwykłe mogą być publiczne lub przyjazne.
Klasy mogą być zdefiniowane wewnątrz metody, a nawet wewnątrz wydzielonego nawiasami łańcuchowego zakresu. (Szczegóły w "Thinking in JAVA".)
Anonimowe klasy wewnętrzne
Zanim omówimy anonimowe klasy wewnętrzne, prześledzimy skróconą wersję przykładu poprzedniego. Korzystamy z interfejsu InterDrugi i analizujemy poniższy program(P32). Klasa DoAnonim została napisana tylko po to by utworzyć referencję i zwrócić ją (no i oczywiście po to, by napisać implementację interfejsu). Nazwa tej klasy nie jest nigdzie później dostępna.
P32Anonim.java
Można połączyć tworzenie referencji z definicją klasy. W przykładzie poniższym (P33) budowana jest metoda klasy interfejsu InterDrugi o nazwie dajDrugi(), której zadaniem jest zwrócenie referencji i jednocześnie zbudowanie implementacji tego interfesu. Tę konstrukcję można zastosować również w przypadku dziedziczenia. Żeby zbudować nowy obiekt trzeba użyć operatora new i konstruktora (w tym przypadku bezargumentowego), ale żeby go użyć trzeba zbudować klasę implementującą ten interfejs i dopisać brakujący średnik.
P33Anonim.java
Jeśli klasa bazowa zawiera konstruktor z argumentem, to klasa wewnętrzna wywiedziona z niej musi to również uwzględniać. Zbudujemy klasę BazowaJeden, która będzie rozszerzana w klasie wewnętrznej.
Program z klasą wewnętrzną dziedziczącą po klasie bazowej jest zapisany poniżej(P34).
P34Anonim.java
W tym przykładzie klasa wewnętrzna ona nazwie Zbazowej jest zbudowana po to, by rozszerzyć klasę bazową napisać odpowiednią implementację, a następnie w metodzie dajBazowa() udostępnić tę implementację przez zwrócenie referencji do klasy bazowej.
Nazwa klasy (Zbazowej) z poprzedniego przykładu ma modyfikator private nigdzie nie może zostać użyta. Jest więc dobrym kandydatem do utworzenia klasy anonimowej. Tym razem trzeba uwzględnić fakt, że konstruktor klasy bazowej ma argument, jest to pokazane w poniższym przykładzie.
P35Anonim.java
Klasa anonimowa nie może mieć konstruktora, ponieważ nie ma nazwy. Inicjalizacja składowych może być wykonana wraz z definicją. Wartość inicjowana może być też przekazana przez argument (przykład poniżej), ale argument służący do inicjowania wartości w klasach anonimowych musi być finalny.
P36Anonim.java
Jest możliwość bardziej złożonego inicjownia zmiennych za pomocą konstrukcji nazywanej inicjalizacją instancji (przykład poniżej). Inicjalizacja instancji może być tylko jedna, tak więc anonimowe klasy mogą mieć tylko jeden "konstruktor". Warto zwrócić uwagę, że instrukcja if nie może wystąpić przy inicjowaniu wraz z definicją.
P37Anonim.java
Klasy wewnętrzne, podsumowanie
Klasa wewnętrzna ma dostęp do wszyskich pól i metod klasy ją otaczającej. Umożliwia to implementację interfejsów lub dziedziczenie w klasach wewnętrznych pozwalajace na działanie na polach klasy ją otaczającej. Instancja klasy wewnętrznej przechowuje referencję do obiektu klasy otaczającej - tego obiektu, który ją utworzył. Dotyczy to również klas wielokrotnie zagnieżdżonych.
Można tworzyć statyczne klasy wewnętrze. Klasy takie nie mają cechy z poprzedniego punktu. Można stworzyć obiekt statycznej klasy wewnętrznej bez tworzenia obiektu klasy zewnętrznej.
Każda z klas po kompilacji jest zapisywana w pliku nazwa.class, jeśli w klasie nazwa znajduje się klasa wewnętrzna o nazwie wewnetrzna to dodatkowo powstanie plik o nazwie nazwa$wewnetrzna.class. Jeśli klasa nazwa zawiera wewnętrzną klasę anonimową to powstanie plik o nazwie nazwa$1.class. Jeśli wewnętrznych klas anonimowych będzie więcej to powstaną kolejno pliki nazwa$2.class nazwa$3.class ....
Klasa wewnętrzna zwykle dziedziczy z innej klasy lub implementuje interfejs. Może wykonywać operacje na składowych klasy zewnętrzej (ją otaczającą), czyli jest furtką do klasy otaczającej.
Każda klasa wewnętrzna może niezależnie dziedziczyć implementację. Klasa zwykła może dziedziczyć tylko jedną. Biorąc pod uwagę fakt, że klasy wewnętrzne mogą działać na wszystkich składowych klas je otaczających, to widać że można udostępnić wiele implementacji - tak więc jest to możliwość przejmowania implementacji z wielu klas.
Umożliwiają wywołania zwrotne. Tzn., umożliwiają przekazanie obiektowi informacji pozwalającej na późniejsze odwołanie się do obiektu, który tę informację przekazał (odpowiednik wskaźników). Klasy wewnętrzne przechowują referencje do obiektu, który je powołał do życia (w ten właśnie sposób mają dostęp do wszystkich pól klasy ją otaczającej).
Pozwalają na elegancką budowę szkieletu aplikacji. Szkieletem aplikacji nazywamy klasę lub zestaw klas zaprojektowanych do rozwiązywania wybranej grupy problemów. Wykorzystanie istniejącego szkieletu aplikacji polega na dziedziczeniu po jednej lub wielu klasach przesłanianiu wybranych metod, pozwalającym nam na dostosowanie szkieletu do własnych potrzeb.
Szczególnym przypadkiem szkieletu aplikacji jest szkielet sterowania, w którym podstawowym elementem jest obsługa zdarzeń. Biblioteka Swing służąca od budowy GUI jest przykładem takiego szkieletu sterowania.