Diversity w polskim IT
Michał Karmelita
Asseco Poland S.A.
Michał Karmelita Java Developer @ Asseco Poland S.A.

Poznaj podstawy Javy - klasy wewnętrzne

Poznaj klasy wewnętrzne w Javie, czyli takie klasy, które można tworzyć w obrębie innych klas.
27.05.20204 min
Poznaj podstawy Javy - klasy wewnętrzne

Z serii podstaw Javy pojawił się już artykuł o klasach w Javie ogólnie. Dziś mowa będzie o klasach, które tworzyć możemy w obrębie innej klasy.



Na początku warto ustalić obowiązującą w tym zakresie terminologie. Idąc za oficjalną dokumentacją Oracle, wszystkie tego rodzaju klasy określamy zbiorczo jako nested classes, co przetłumaczyć można jako klasy zagnieżdżone. W obrębie tej grupy wyróżnia się dwa podstawowe rodzaje: klasy statyczne oraz niestatyczne. Klasy należące do pierwszej wymienionej grupy nazywane są static nested classes (statyczne klasy zagnieżdżone), zaś należące do drugiej określane są jako inner classes (klasy wewnętrzne). W obrębie tych ostatnich wyodrębnić można jeszcze dwa szczególne rodzaje: local classes (klasy lokalne) oraz anonymous classes (klasy anonimowe).

Struktura opisywanych klas wygląda zatem następująco (trzymając się oryginalnej terminologii):

  • nested classes:
    • inner classes,
      • „normalne” inner classes,
      • local classes,
      • anonymous classes,
    • static nested classes.


Dysponując takim wstępem teoretycznym, możemy przejść do przykładu. Zacznijmy od zwykłej klasy wewnętrznej. Jej deklaracja wyglądać może następująco:

Jak widać na przykładzie, tworząc klasę wewnętrzną do naszej dyspozycji są zarówno jej własne pola, jak i pola klasy zewnętrznej, nawet jeśli są prywatne i nie posiadają metody dostępowej (działa to także w drugą stronę – z klasy zewnętrznej można sięgać do pól klasy wewnętrznej). Dlatego możliwe jest odwołanie się do pola message z poziomu metody printMessage(). Aby wywołać tę metodę, należy utworzyć instancję klasy wewnętrznej. W tym celu dysponować musimy najpierw instancją klasy zewnętrznej. Cała składnia tworzenia obiektu i wywołanie na nim metody printMessage()mogłoby zatem wyglądać np. następująco:

Klasy wewnętrzne zawierać mogą pola o takich samych nazwach, jak pola klasy zewnętrznej. Rozwiązaniem problemu powielających się nazw może być użycie słowa this, które w przypadku odwołania się do pola klasy zewnętrznej, należy poprzedzić jej nazwą:

Jako ciekawostkę dodać można, że Java pozwala na tworzenie kolejnych stopni zagnieżdżenia, analogicznie, jak na poniższym przykładzie:

Dodatkowo wspomnieć należy, że klasy wewnętrzne mogą być również opatrzone innym modyfikatorem dostępu niż wymieniony na przykładzie public (mogą być np. prywatne i wówczas nie będzie można powołać do życia ich instancji poza klasą, w której są zdefiniowane). Klasy takie mogą być także finalne lub abstrakcyjne.

Kolejny rodzaj klasy zagnieżdżonej – local class – to klasa, która zdefiniowana jest w ciele metody klasy zewnętrznej, np. w taki sposób:

Na powyższym przykładzie w ciele metody printMessage() tworzymy klasę LocalClass, powołujemy do życia jej instancję oraz wywołujemy na nie metodę printFromLocalClass(). Z poziomu klasy lokalnej mamy dostęp do pól klasy zewnętrznej oraz do zmiennych lokalnych metody, w której są zagnieżdżone. W tym ostatnim przypadku zmienne muszą być jednak finalne (przed Javą 8 konieczne było jawne oznaczanie takich zmiennych jako final, od Javy 8 wystarczy, że są efektywnie finalne, czyli nie zmieniają wartości po tym, jak zostały raz zainicjalizowane).

Kolejnym rodzajem klas wewnętrznych są klasy anonimowe. Spójrzmy na poniższy przykład:

Zdefiniowaliśmy w nim interfejs Printer zawierający metodę print(). Chcąc wykorzystać tę metodę, powinniśmy co do zasady stworzyć klasę implementującą taki interfejs oraz zawartą w nim metodę. Możemy jednak wykorzystać do tego także klasę anonimową. Implementacja wyglądać mogłaby w takim wypadku następująco:

Na powyższym przykładzie powołujemy do życia obiekt przypisany do zmiennej typu interfejsu Printer. Niemożliwe jest jednak zainicjalizowanie takiej zmiennej instancją interfejsu. Dlatego ad hoc tworzymy niezbędną klasę, umieszczając jej ciało między nawiasami klamrowymi. Jak łatwo zauważyć, klasa taka nie ma nazwy. W dalszej części kodu możemy już normalnie korzystać z utworzonej zmiennej.

W klasach anonimowych, podobnie, jak we wcześniejszym przykładzie, można korzystać ze zmiennych klasy zewnętrznej oraz finalnych zmiennych metody, w której zdefiniowano klasę anonimową. Klasa anonimowa implementować musi wszystkie metody interfejsu; może ona dodatkowo definiować własne pola lub metody.

Warto wspomnieć, że od Javy 8 takie proste implementacje w postaci klas anonimowych straciły na znaczeniu, ponieważ mogą być zastępowane wyrażeniami lambda. Powyższy przykład można byłoby uprościć np. do:

Albo nawet jeszcze prościej, stosując referencję do metody:

W przypadku nieco bardziej złożonych interfejsów klasy anonimowe pozostają jednak znakomitą alternatywą dla „normalnych” implementacji. 

Ostatnim rodzajem klas zagnieżdżonych są static nested classes. Zasadniczą cechą odróżniającą je od „zwykłych” klas wewnętrznych jest to, że mają one dostęp jedynie do statycznych pól klasy zewnętrznej. W przeciwnym wypadku mogłoby dojść do sytuacji, w której odwołujemy się do niestatycznej zmiennej klasy zewnętrznej, mimo że żaden obiekt tej klasy nie został jeszcze utworzony. Przykładowa zagnieżdżona klasa statyczna może wyglądać następująco:

Poszczególne rodzaje klas zagnieżdżonych są w praktyce stosowane częściej lub rzadziej, bez wątpienia jednak są w stanie dostarczyć programiście wielu korzyści. Ich użycie pozwala na logiczne pogrupowanie struktury klas, „uszczelnienie” enkapsulacji (poprzez tworzenie klas prywatnych) czy ogólnie – pisanie bardziej czytelnego, łatwiejszego w utrzymaniu kodu.

<p>Loading...</p>