Adnotacje

Mieliście kiedyś tak, że po użyciu podstawowych adnotacji takich jak np. @Override zastanawialiście się dlaczego tak naprawdę to robimy? Przecież pomijając tą jedną, pozornie nic nie znaczącą linijkę kodu, wszystko zadziałałoby tak samo, prawda?

I tak i nie, w tym poście postaram się wam pokazać podstawowe zastosowania adnotacji, oraz możliwości które są nam razem z nimi dostarczane.

Budowa oraz zastosowanie adnotacji

Tak jak zdążyliście wcześniej zauważyć znak małpy @ mówi naszemu kompilatorowi, że to co po nim występuje jest adnotacją.

Na samym początku podzielimy je sobie ze względu na budowę:

Jak można łatwo zauważyć w powyższym przykładzie, w przypadku gdy naszym jedynym elementem jest pole „value” możemy je pominąć i od razu wpisać jego wartość.

Znamy już budowę, ale co z zastosowaniem? Właściwie odpowiemy sobie na pytanie czym są adnotację. Są to znaczniki typu metadata (metadane), czyli dane o danych. Dzięki nim dostarczamy kompilatorowi bądź też programowi wykonywanemu dodatkowe informacje na temat konkretnej klasy, obiektu czy zmiennej.

Podstawowe adnotacje

Razem z bibliotekami javy dostarczane nam jest 7 podstawowych adnotacji, z czego trzy są częścią java.lang, a pozostałe cztery mogą zostać zaimportowane razem z java.lang.annotation.

Adnotacje odnoszące się do kodu:
@Override – sprawdza czy metoda którą właśnie implementujemy zostaje przez nas nadpisana. Jeżeli nie istnieje ona w interfejsie bądź też klasie po której dziedziczymy, zwraca błąd.

Teraz pytanie które zrodziło się w mojej głowie na samym początku, co złego może się wydarzyć? Jaki to ma cel?

Wyobraźmy sobie, że dziedziczymy po klasie, która wymaga od nas implementacji metody equals. Wymaga ona właściwie jej małą modyfikację potrzebną, do poprawnego porównywania naszych obiektów (dochodzą jakieś dodatkowe dane, zmienne). My jednak nieświadomie, przypadkowo zamiast zaimplementować metodę equals(Object obj) implementujemy equal(Object obj). Niby literówka, kompilator jednak nie widzi tutaj błędu. Problemem jest to, że inne klasy wykorzystają equals z klasy po której dziedziczymy, sprawiając tym samym, że nasz program nie będzie działał tak jak powinien.

Jednak w przypadku zapisu:

Nasz kompilator zauważyłby, że próbujemy nadpisać metodę która jeszcze nie istnieje informując nas tym samym o naszej pomyłce.

Dodatkowo jest to czytelny sposób do informowania osób z którymi pracujemy, o tym, że ta konkretna metoda rozszerza jakiś interfejs. Dotyczy się to też nas samych, gdy po dłuższym czasie będziemy z jakiegoś powodu musieli powrócić do naszego kodu, o wiele szybciej przeanalizujemy nasz kod.

@SuppressWarnings – dzięki tej adnotacji możemy ukryć ostrzeżenia które występują na poziomie kompilacji.

Słowo „ukryć” jest tutaj kluczowe, ponieważ musimy być świadomi tego, że nie rozwiązuje ono za nas problemów. Weźmy na przykład tablicę obiektów, którą rzutujemy na tablicę typu generycznego.

Po napisaniu takiego kodu naszym oczom ukaże się wielki napis
Unchecked cast: 'Java.lang.Object[]' to 'T[]'. Sugeruje on, że w przypadku gdy do naszej tablicy spróbujemy włożyć obiekt niepasującego typu nastąpi konflikt. Oczywiście takie rzeczy mogą być przez nas weryfikowane, no ale to nie jest temat na dzisiejszy wpis. Po uzupełnieniu naszego kodu o odpowiednią adnotację @SuppressWarnings("unchecked") zaznaczymy kompilatorowi, że jesteśmy świadomi tego co zrobiliśmy, do tego jesteśmy pewni, że taka sytuacja nie wystąpi.

Powinniśmy także być świadomi tego, że po objęciu taką adnotacją jakiegoś elementu, obejmuje także ona wszystkie elementy które się w nim zajmują. Na przykład jeżeli zastosujemy tą adnotację na jakiejś klasie, wszystkie znajdujące się w niej zmienne oraz metody także będą ją objęte. Z tego powodu w przypadku gdy zdecydujemy się skorzystać z tej opcji powinniśmy to zrobić możliwie jak najbardziej ‚głęboko’ w kodzie.

@Deprecated – trzecia podstawowa adnotacja zwraca ostrzeżenie w przypadku, jeżeli oznaczona przez nas fragment kodu (metoda) zostanie użyty. Jest on wtedy oznaczony jako ‚nieaktualny, przestarzały” i daje nam do zrozumienia w postaci ostrzeżenia, że potrzebne są pewne poprawki w kodzie.

Tworzenie własnej adnotacji

Jeśli chodzi o tworzenie własnej adnotacji, nie różni się to wiele od deklaracji prostego interfejsu, z tą różnicą, że dodajemy znak @ przed słowem interface.

Budowa pól naszej adnotacji to MODYFIKATOR_DOSTĘPU NAZWA() default WARTOŚĆ_DOMYŚLNA

Dodatkowo do takiego „interfejsu” powinniśmy dodać parametry ograniczające, dokładnie zaznaczając w których miejscach możemy zastosować naszą adnotację (@Target) oraz modyfikator zaznaczający jak długo nasza adnotacja ma być przetrzymywana (@Retention).

– Target
W zależności od tego parametru deklarujemy gdzie nasza adnotacja może zostać zastosowana, mamy do dyspozycji takie możliwości:
ElementType.ANNOTATION_TYPE
ElementType.CONSTRUCTOR
ElementType.FIELD
ElementType.LOCAL_VARIABLE
ElementType.METHOD
ElementType.PACKAGE
ElementType.PARAMETER
ElementType.TYPE – dla klas oraz interfejsów

Możemy także zadeklarować ją jako tablicę wartości definiując więcej niż jedno miejsce, gdzie nasza adnotacja może zostać zastosowana,
np. @Target({ ElementType.METHOD, ElementType.FIELD })

– Retention
W tym przypadku decydujemy jak długo nasza adnotacja ma być przechowywana, są to parametry:
RetentionPolicy.CLASS – adnotacja przetrzymywana jedynie w pliku .class po kompilacji, nie jest jednak widoczna podczas wykonywania programu (wartość domyślna)
RetentionPolicy.RUNTIME – adnotacja jest przetrzymywana do samego końca, jest dostępna w trakcie wykonywania programu
RetentionPolicy.SOURCE – adnotacja przetrzymywana jedynie w kodzie źródłowym programu, zostaje usunięta przez kompilator w momencie kompilacji.

Jak wykorzystać „naszą” adnotację

Tutaj zaczynają się malutkie schody. Aby pobrać wartość naszych adnotacji musimy skorzystać z mechanizmu refleksji. Jest to temat na inny wpis. Pokażę wam teraz przykładowy kod wyświetlenia takiej wartości, nie jest to nic skomplikowanego, wystarczy na spokojnie przeanalizować sobie ten krótki kod.

W tym przypadku odwołujemy się kolejno do: Całej klasy -> metody o nazwie „getAnnotationValues”, pobieramy jej adnotacje i wywołujemy konkretną metodę, która zwraca nam wartość typu boolean.

Zastosowanie adnotacji

I docieramy do samego końca tego wpisu. Odpowiemy sobie tutaj jednak na bardzo ważne pytanie, gdzie warto stosować adnotacje? Ciężko znaleźć dla nich zastosowanie w małych, codziennych projektach. Warto jednak zaznaczyć, że odgrywają one ważną rolę w takich frameworkach jak Spring (Spring framework) oraz Hibernate (Hibernate framework). Jest to wystarczający powód, abyśmy poznali sposób działania i nie dali się zaskoczyć. Nigdy nie wiadomo, kiedy ta wiedza okaże nam się przydatna.

One Comment, RSS

  1. Wyrażenia lambda – premier.dev 30 kwietnia 2017 @ 23:34

    […] jest taka, że wraz z Javą 8 dostaliśmy adnotację (wpis o adnotacjach) @FunctionalInterface dzięki której możemy zrzucić na kompilator odpowiedzialność związaną […]

Your email address will not be published. Required fields are marked *

*