adnotacje w języku Java
Adnotacje w języku Java
3 października 2016
testy jednostkowe junit
Testy jednostkowe z JUnit
29 października 2016
klasy wewnętrzne i anonimowe

W artykule tym przeczytasz o klasach wewnętrznych i klasach anonimowych w Javie. Dowiesz się jak wyglądają, jakie mają ograniczenia oraz kiedy możemy ich używać. Na końcu, jak zwykle, czeka na Ciebie zestaw zadań, w których przećwiczysz materiał z tego artykułu.

To jest jeden z artykułów w ramach darmowego kursu programowania w Javie. Proszę zapoznaj się z pozostałymi częściami, mogą one być pomocne w zrozumieniu materiału z tego artykułu.

Na początku pewne zastrzeżenie. W całym artykule posługuję się określeniem „klasy wewnętrzne”. Ważne jest żebyś zdawał sobie sprawę z tego, że równie dobrze możemy mieć do czynienia z wewnętrznym typem wyliczeniowym czy wewnętrznym interfejsem. Poznając klasy wewnętrzne, poznajesz także „interfejsy wewnętrzne” czy „wewnętrzne typy wyliczeniowe”.

Istnieje kilka typów klas wewnętrznych:

  • (standardowe) klasy wewnętrzne,
  • statyczne klasy wewnętrzne,
  • lokalne klasy wewnętrzne,
  • anonimowe klasy wewnętrzne.

Często mówimy po prostu o klasie wewnętrznej odwołując się do którejkolwiek z powyższych. W kolejnych akapitach postaram się pokazać różnice pomiędzy tymi typami klas.

Klasy wewnętrzne

Standardowe klasy już znasz. Ot zwykłe public class Example {} i już mamy klasę. A czym jest klasa wewnętrzna? Zacznijmy od przykładu:

W naszym przykładzie widzisz dwie klasy. Standardowa klasa OuterClass i klasa wewnętrzna InnerClass.

Podobnie jak w przypadku atrybutów czy metod, klasy wewnętrzne mogą mieć standardowe modyfikatory dostępu public, protected czy private. Brak modyfikatora dostępu także i tutaj jest poprawny.

Modyfikatory dostępu użyte przed definicją klasy wewnętrznej działają identycznie jak w przypadku atrybutów, metod czy konstruktorów. Jeśli chcesz przeczytać o nich więcej osobny akapit na ich temat znajdziesz w artykule o dziedziczeniu (http://www.samouczekprogramisty.pl/dziedziczenie-w-jezyku-java/).

Ważne jest także to, że klasa wewnętrzna ma dostęp do wszystkich atrybutów czy metod klasy zewnętrznej, w której została zdefiniowana.

Tworzenie instancji klasy wewnętrznej

Do stworzenia instancji klasy wewnętrznej potrzebujemy instancji klasy zewnętrznej. Proszę spójrz na przykład poniżej.

Widzisz tam typ OuterClass.InnerClass, to nic innego jak odwołanie się do typu wewnętrznego. W tym fragmencie kodu tworzymy dwie instancje. Pierwsza z nich powstaje w wyniku wywołania metody instantiate z klasy OuterClass. Ciało tej metody możesz zobaczyć w poprzednim fragmencie kodu.

Proszę zwróć uwagę, że wewnątrz metody instantiate nie musimy podawać pełnej nazwy klasy, samo new InnerClass() wystarczy (jest to odpowiednik this.new InnerClass()).

instance2 tworzymy posługując się instancją klasy OuterClass. Taka konstrukcja jest niezbędna w przypadku standardowych klas wewnętrznych.

Statyczne klasy wewnętrzne

W języku Java istnieją także statyczne klasy wewnętrzne. Są to klasy wewnętrzne poprzedzone modyfikatorem static. Proszę spójrz na przykład poniżej.

Jak widzisz przykład ten jest bardzo podobny do pierwszego z tego artykułu. Nowością tutaj jest modyfikator static, reszta pozostaje bez zmian.

Ważna jest natomiast różnica przy tworzeniu instancji statycznej klasy wewnętrznej.

Domyślnie, wszystkie wewnętrzne interfejsy i typy wyliczeniowe są statyczne, modyfikator static jest przed nimi zbędny (możesz spróbować go dodać, IDE powinno zwrócić Ci na to uwagę).

Tworzenie instancji statycznej klasy wewnętrznej

W odróżnieniu od standardowych klas wewnętrznych, nie potrzebujemy instancji klasy zewnętrznej do stworzenia instancji statycznej klasy wewnętrznej. Może się to wydać trochę skomplikowane jednak całość na pewno będzie bardziej zrozumiała gdy popatrzysz na przykład.

Różnica jest taka, że wystarczy nam po prostu pełne odwołanie się do typu klasy wewnętrznej aby stworzyć jej instancję. W naszym przypadku jest to new OuterClass2.InnerClass2().

Lokalne klasy wewnętrzne

Jako ostatni typ klas wewnętrznych zostały nam lokalne klasy wewnętrzne. I wiesz co? W sumie poza tym, że możemy je zdefiniować wewnątrz bloku (wewnątrz metody, bloku if itp.) i nie poprzedzają ich modyfikatory dostępu (public, private, protected) niczym szczególnym nie różnią się od pozostałych klas wewnętrznych. Proszę spójrz na przykład:

Tutaj wewnątrz metody tworzymy naszą lokalną klasę wewnętrzną LocalClass. Linijkę później tworzymy jej instancję i wywołujemy na niej metodę.

Głównym ograniczeniem/zaletą klas lokalnych jest ich zasięg. Podobnie jak w przypadku zmiennych lokalnych, dostęp do klas lokalnych jest wyłącznie w bloku, w którym zostały zdefiniowane.

Kiedy używać klas wewnętrznych

Właśnie, po co w ogóle są nam one potrzebne? Mam nadzieję, że przykład ze standardowej biblioteki Javy pomoże Ci to zrozumieć.

W artykule o kolekcjach opisałem mapę i sposób w jaki możemy po niej iterować.

W naszej mapie trzymamy nazwę miesiąca i odpowiadającą mu liczbę dni. Każda instancja obiektu implementującego interfejs Map posiada metodę entrySet, która zwraca typ Set<Map.Entry<K, V>>.

Rozłóżmy ten typ na części pierwsze. K to nasz klucz (ang. key), V to wartość (ang. value) przechowywana w mapie. Map.Entry<K, V> to typ generyczny który parametryzowany jest typem klucza i wartości. Set<Map.Entry<K, V>> to zbiór elementów mapy. Każdy element ma klucz i wartość. A czym jest Map.Entry? To nic innego jak interfejs wewnętrzny 🙂 Jest to interfejs Entry zdefiniowany wewnątrz interfejsu Map.

Więc po co używać klas wewnętrznych? Powodów jest kilka. Jak w przykładzie z Map.Entry dobrym pomysłem użycia klas wewnętrznych jest sytuacja, w której klasa wewnętrzna nie ma sensu bez klasy zewnętrznej i jest z nią ściśle związana.

Kolejnym powodem może być lepsza enkapsulacja kodu (ukrywanie szczegółów działania klasy wewnątrz). Dzięki temu, że klasy wewnętrzne mają dostęp nawet do prywatnych zasobów klas otaczających, te drugie możemy bardziej „opakować”. Ukryć więcej szczegółów wewnątrz.

Klasy anonimowe

Zacznijmy od prostej definicji. Klasy anonimowe to klasy definiowane w kodzie, które mają dokładnie jedną instancję. Definicja klasy anonimowej połączona jest z tworzeniem jej jedynej instancji. Klasy anonimowe zawsze są klasami wewnętrznymi.

Proszę spójrz na przykład poniżej:

Na początku definicja interfejsu z jedną metodą sayHello. Ciekawsze są jednak ostatnie cztery linijki. To właśnie definicja klasy anonimowej.

Konstrukcja ta pozwala nam na stworzenie instancji klasy anonimowej. W naszym przykładzie tworzymy nową klasę, która implementuje interfejs GreetingModule oraz tworzymy jej nową instancję przy pomocy słowa kluczowego new.

Wewnątrz definicji klasy anonimowej możemy definiować atrybuty czy metody. W praktyce sprowadza się to przeważnie do zaimplementowania metod interfejsu dla którego tworzymy klasę anonimową.

W większym fragmencie kodu użycie klas anonimowych może wyglądać następująco.

W naszym przykładzie tworzymy dwie instancje robotów jan i john, które używają innych „modułów powitań”. Każdy z nich jest instancją anonimowej klasy wewnętrznej.

Niektóre z klas anonimowych można zastąpić wyrażeniami lambda, o których przeczytasz w jednym z kolejnych artykułów.

Używanie zmiennych z klas zewnętrznych

Wewnątrz definicji klas wewnętrznych (także klas anonimowych) możemy używać zmiennych z otaczającego je kontekstu. Spójrz na przykład poniżej:

W metodzie saySomething używamy dwóch zmiennych z klasy otaczającej finalVariable i effectivelyFinalVariable. Jest jednak jedno ograniczenie. Zmienna z „zewnątrz” użyta w klasie wewnętrznej musi być finalna albo „właściwie finalna”.

Zmienna jest finalna jeśli poprzedza ją słowo kluczowe final. Kiedy jest „właściwie finalna”? Kiedy nie zmieniamy jej wartości i kompilator za nas wstawia brakujące słowo final ;).

W związku z tym użycie zmiennej nonFinalVariable nie jest dozwolone ponieważ jej wartość jest zmieniana.

Zadania

Na koniec czekają na Ciebie dwa zadania, w których przećwiczysz zagadnienia omówione w artykule. Przygotowałem też zestaw przykładowych rozwiązań i umieściłem je na githubie. Jak zwykle zachęcam do samodzielnego rozwiązywania zadań, wtedy nauczysz się najwięcej. Samo przeczytanie artykułu nie wystarczy, do dzieła!

  1. Rozszerz przykład z robotami z akapitu o klasach anonimowych o robota witającego się w innym języku np. niemieckim.
  2. Zadanie to będzie wymagało dodatkowej lektury na temat interfejsu Comparator ze standardowej biblioteki Javy. Pobierz od użytkownika 5 wyrazów, zapisz je w List<String>. Użyj metody Collections.sort, przekazując jako argumenty listę oraz klasę anonimową, która posortuje ją na podstawie długości wyrazów (najkrótsze wyrazy powinny być pierwsze). Do sprawdzenia długości słowa możesz użyć metody String.length. Wyświetl zawartość listy przed i po sortowaniu.

Materiały dodatkowe

Przygotowałem też dla Ciebie zestaw materiałów dodatkowych zawierających informacje na temat klas wewnętrznych i anonimowych. Dodatkowo wszystkie przykłady kodu użyte w tym artykule możesz znaleźć na samouczkowym githubie.

Podsumowanie

Bardzo się cieszę, że przeczytałeś artykuł do końca. Po lekturze artykułu wiesz czym są klasy wewnętrzne. Wiesz też jakie rodzaje klas wewnętrznych występują. Znasz także klasy anonimowe i wiesz kiedy ich używać. Rozwiązując zadanie przećwiczyłeś całość w praktyce. Innymi słowy kawał solidnej wiedzy 🙂

Na koniec mam do Ciebie prośbę. Proszę podziel się artykułem ze znajomymi i polub moją stronę na facebooku. Zależy mi na dotarciu do jak największej liczby osób, które chcą uczyć się programowania. Z góry dziękuję i do następnego razu.

[FM_form id=”3″]

Zdjęcie dzięki uprzejmości https://www.flickr.com/photos/alfmelin/

  • Świerzak

    Zrobiłem trochę inaczej to zadanie 2 🙂

    • Marcin Pietraszek

      Jeśli chciałeś wypisać zawartość listy w jednej linijce mogłeś użyć metody toString zaimplementowanej w ArrayList – osobna metoda nie byłaby potrzebna (format wyjściowy jest trochę inny). Jeśli natomiast chciałbyś zostać przy swoim formacie proszę przeczytaj artykuł o StringBuilder – w sumie też mogłem go użyć w przykładowej implementacji ;).

      Dodatkowo proszę staraj się nazywać metody zgodnie z tym co robią. Staraj się jednocześnie stosować do konwencji nazewniczej – z wielkiej litery piszemy nazwy klas. Do metod używaj „camelCase”.

      No i gratulacje za rozwiązanie zadania, zabieraj się teraz za kolejne ;).

      • Świerzak

        Dzięki za cenne wskazówki 🙂 Faktycznie nie wiem czemu napisałem z dużej litery nazwę metody, chociaż wiem że tak się nie robi 🙂