V tomto článku bych chtěl nastínit, co jsou to java anotace, k čemu jsou dobré a jak s nimi pracovat. Také se zlehka podíváme na obsah balíku java.lang.annotation

Anotace je v javě zápis, jak přiřadit nějakému elementu (třída, metoda, proměnná) jakýsi příznak, metadata, informaci mimo běžný kód. Na první pohled se zdá vysvětlení dost kostrbaté, Pojďme si tedy hned ukázat něco, co smysl dává.

Základy anotací

Jedna z běžně používaných anotací je například @Deprecated

public
class Thread implements Runnable {
...
@Deprecated
    public final void stop() {
  synchronized (this) {
...

Ukázka z třídy Thread. Povšimněte si zápisu @Deprecated nad metodou stop(). Ta nám říká, že metoda stop je z nějakého důvodu nedoporučovaná, a v budoucnu nebude pravděpodobně podporována. V JavaDoc komentáři se většinou pak najde vysvětlení, proč tomu tak je, a jak danou funkčnost vyřešit jinak.

Další z používaných anotací standardně obsažených v javě je @Transient, značící, že proměnná nebude ukládána při serializaci.

Ukázali jsme si tedy základní zápis anotace bez parametru @NázevAnotace. Takové anotace si můžeme bez problémů napsat sami.

Definice takové jednoduché bezparametrické anotace by mohla vypadat například takto:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NeedsRootPermissions {
}

Co vlastně jednotlivé řádky znamenají :
@Retention(RetentionPolicy.RUNTIME) – anotace, která říká, že vaše nově vytvořená anotace bude dostupná za běhu.
@Target(ElementType.TYPE) – anotace, která říká, že anotace jde použít nad třídou, rozhraním nebo enumem.

public @interface NeedsRootPermissions – samotná definice anotace, veřejná anotace (@interface) se jménem NeedsRootPermissions

Taková anotace se teď dá velmi snadno připojit ke kterékoli třídě.

Představme si, že vyvíjíme aplikaci, kde jsou uživatelé a také superuživatelé. Přístup k některým třídám chceme omezit jen na superuživatele.

@NeedsRootPermissions
public class CreateUser extends AbstractProcess {
...

Anotací jsme označili naší vlastní třídy CreateUser. Na první pohled je vidět, že třída dává smysl pouze pokud máte nastavena práva superuživatele (nemá nic společného s operačním systémem, je to jen příznak ve vaší aplikaci). A jen na vás je, jak s takovou anotací naložíte. Možná že Vám bude stačit jen jako dokumentační anotace pro potřeby vývoje. Ale java nabízí slušné api. Anotaci tak nad třídou můžeme zjistit například:

Class<?> clazz = this.getClass();
if (clazz != null) {
    NeedsRootPermissions annotation = clazz
               .getAnnotation(NeedsRootPermissions.class);
    if (annotation != null) {
        checkAccess();
        ....

Co tedy provádíme:
vezmeme aktuální třídu (this.getClass()), a nad ní se pokusíme získat anotaci námi požadovaného typu NeedsRootPermission (clazz.getAnnotation(NeedsRootPermissions.class)). Pokud se vše povede a anotace neni rovna null, víme, že anotace je nad současnou třídou clazz přítomna. Ještě máme k dispozici metodu isAnnotationPresent(NeedsRootPermissions.class), která nám poví jen zda je anotace přítomna. Ta by byla užitečná v předchozím případě. Ovšem to, že získáme objekt anotace záhy velmi oceníme

To je nejzákladnější použití anotací. Teď se podíváme na něco lepšího, na anotace nesoucí námi dané hodnoty.

Anotace s hodnotami

Definice anotace, tentokrát z webového světa by mohla vypadat například takto:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
/**
 * Trida bude vracet chybovy stav uvedeny ve {@code value}.
 */
public @interface ErrorPage {
    int value();
}

Taaak, co se změnilo. Přibyla definice metody value(). Ta říká, že anotace bude vyžadovat jeden parametr typu int. Použití pak může vypadat takto:

@ErrorPage(404)
public class EcErrorPage extends AbstractScreen {

Zápis je volitelně možný ještě způsobem který popíšeme o maličko pozdeji, tedy @ErrorPage(value = 404), kde specifikujeme, které metodě předáváme hodnotu. Pro jednohodnotové anotace se ale v zásadě vždy používá metoda value() a možnost vynechat přiřazení, tedy @ErrorPage(404).

Pokud chceme v anotaci přiřadit více hodnot, pak definice vypadá nějak takto

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Author {
    String name();
    String email();
}

a její použití v pak bude @Author(name = „Tomas Dvorak“, email=„todvora­@gmail.com“)

Class<?> clazz = this.getClass();
if (clazz != null) {
    Author annotation = clazz.getAnnotation(Author.class);
    if (annotation != null) {
        String name = annotation.name();
        String email = annotation.email();
        setOnErrorContact(name, email);
        ....

Defaultní hodnoty

Defaultní hodnota anotaci se dá nastavit při jejím definování

public @interface Author {
    String name() default "unknown developer";
    String email() default "[email protected];
}

a při použití anotace je pak možné hodnoty, které mají default nastaveno vynechat. Například @Author(email=„[email protected]“).

Anotace pro anotace

K anotacím se nezbytně váže několik dalších standardních anotací. Jsou to

  • @Retention
  • @Target
  • @Inherited

Nejjednodušší, @Inherited, říká, že každá třída, která je potomkem náší anotací označené třídy bude mít takovou anotaci přiřazenu automaticky též.
@Retention – k retention se váže enum RetentionPolicy. Jeho možné hodnoty jsou

  • SOURCE
  • CLASS
  • RUNTIME

a vyjadřují možnost přístupu k anotaci. První, SOURCE, označuje anotace, které jsou dostupné jen v kódu, po přeložení již nikoli. Poslední, RUNTIME, značí anotace dostupné reflexí za běhu, to je chování, které využíváme v celém příkladu.

@Target se váže k enumu ElementType, který má hodnoty

  • TYPE
  • FIELD
  • METHOD
  • PARAMETER
  • CONSTRUCTOR
  • LOCAL_VARIABLE
  • ANNOTATION_TYPE
  • PACKAGE

a vyjadřují, k čemu je možné anotaci připojit.

Proč to všechno

Důvodů se nabízí několik. V kódu to vypadá lépe. Lépe se k tomu přistupuje. Nabízí se možnost vytvořit rozhraní a to implementovat tam, kde bychom použili anotaci. Ale implementace jsou otravné, dlouhé, nepřehledné. Přístup k nim taky není nijak super, přece jen if(myClass instanceof MyInterface)… vypadá hůř než getAnnotation() metoda. Anotace se dají využít nejen za běhu, ale i při překladu, například k automatickému generování čehosi. Jsou dostupné nad všemi myslitelnými typy v javě. Není potřeba nic programovat, funguje to vše samo. Spoustu dalších použití vás určitě napadne. Jako jedno z nejobvyklejších je objektově relační mapování, kde nad konkrétními proměnnými říkáme anotací, který sloupec z databáze se na ní má mapovat. Deprecation zná asi také každý.

Závěrem se dá říci, že anotace jsou mocnou zbraní od javy 1.5.