Annotatsioon

Kirjeldus

Annotatsion on märgend, mis annab kompilaatorile ja Java Virtuaalmasinale lisainfot mingi klassi, meetodi, liidese või välja kohta.

Annotatsioone saab kasutada:

  • Informatsiooni andmiseks kompilaatorile, mille abil on võimalik kas tuvastada vigu või vaigistada hoiatusi.

  • Kompileerimise ajal koodi genereerimiseks

  • Koodi käivitamise ajal info edastamiseks ja käsitlemiseks

Javas on nii sisseehitatud annotatsioone (nt: @Override, @SuppressWarnings, @Deprecated) kui ka on võimalik ise koostada märgendeid.

Sisseehitatud annotatsiooid

@Override

@Override on kõige sagedamini kasutatud annotatsioon. Selle eesmärk on teada anda ning kindlaks teha, et alamklassi meetod kirjutab ülemklassi meetodi üle. Kui mingil põhjusel seda ei juhtu, tekib kompileerimise ajal viga.

class ParentClass {
    void method() {
        // Do something
    }
}

class SubClass extends ParentClass {
    @Override
    void method() {
        // Do something else
    }
}

@SuppressWarnings

@SuppressWarnings kasutatakse, et vaigistada kompilaatori poolt antud hoiatusi.

import java.util.*;

class ExampleClass {

    @SuppressWarnings("rawtypes")
    public static void main(String[] args) {
        List list = new ArrayList();   // Should throw a compilation warning, but because it's supressed, nothing happens
        // Do something with the list
    }
}

Antud näites üritame luua List tüüpi objekti ilma talle määramata mingit tüüpi (ehk siis meil on List<String> asemel lihtsalt List). Tavaliselt kompilaator hoiatab meid antud olukorras, kuid kuna meetodil on peal märgend @SuppressWarnings, siis antud hoiatust eiratakse ning vaigistatakse maha.

Mitme hoiatus-tüübi vaigistamiseks tuleb koostada massiiv sõnedest:

@SuppressWarnings({"rawtypes", "deprecated", "unchecked"})

Ideaalis sellist lähenemist võiks vältida ning koodi võiks korda teha, kuid vahest on olukordi, kus me ei saa antud hoiatustega midagi peale hakata.

@Deprecated

@Deprecated märgendiga anname märku, et antud klass/meetod on aegunud ning selle kasutamist võiks vältida. Samuti tuleviks võidakse see koodist ära eemaldada. Antud märgendiga märgitud meetodite kasutamisel kompilaator hoiatab meid.

class ExampleClass {
    @Deprecated()
    static void oldMethod() {
        // Code goes here
    }

    static void newAndBetterMethod() {
        // Code goes here
    }

    public static void main(String[] args) {
        oldMethod();
        newAndBetterMethod();
    }
}

Antud näites deklaleerisime kaks meetodit, ühele lisasime juurde @Deprecated märgendi. Kui üritada nüüd seda meetodit kasutada, siis kompilaator annab meile sellest teada.

../_images/deprecated.png

Soovituslik oleks ära märkida ka, mis versioonist alates antud meetod/klass aegunud on ning kas seda kavatsetakse tulevikus ka eemaldada.

@Deprecated(since = "1.1", forRemoval = true)

Annotatsioonide koostamine

Algeline koostamine

Annotatsioone on võimalik ka ise koostada, selleks luuakse liides @interface elemendiga.

@interface MyAnnotation {}

Järgnevalt saame kasutada loodud märgendit sedasi:

@MyAnnotation
void someMethod() {}

Väärtuste lisamine

Märgenditele saame väärtusi juurde lisada järgnevalt

@interface MyAnnotation {
    String someStrValue();  // Field with no default value
    int valueWithDefault() default 1;   // Field with default value
}

Ning kasutada saame järgnevalt:

@MyAnnotation(someStrValue="abc",valueWithDefault=2)    // valueWithDefault doesn't have to be called out, because it has a default value
void someMethod() {}

Sisseehitatud kirjeldavad annotatsioonid

Ise loodud märgenditele tasub juurde lisada ka kirjeldavad märgendid. Nendeks on: @Target, @Retention, @Inherited, @Documented

@Target

@Target kasutatakse, et määrata ära millistele osadele programmis antud märgend kehtib. Kui määrata loodud märgend valesse kohta, tekib kompilleerumisviga.

Võimalikud väärtused on: TYPE, FIELD, METHOD, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PARAMETER

ElementType.TYPE - Võimalik kasutada klassi, liidese või enumi kohal

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

@Target(ElementType.TYPE)
@interface MyAnnotation {}

// Somewhere in the code

@MyAnnotation
class ExampleClass {}

ElementType.FIELD - Klassisiseste väljade kohal

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

@Target(ElementType.FIELD)
@interface MyAnnotation {}

// Somewhere in the code

class ExampleClass {
    @MyAnnotation
    int value = 10;
}

ElementType.METHOD - Meetodite kohal

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

@Target(ElementType.METHOD)
@interface MyAnnotation {}

// Somewhere in the code

class ExampleClass {

    @MyAnnotation
    void someMethod() {}
}

ElementType.CONSTRUCTOR - Konstruktori kohal

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

@Target(ElementType.CONSTRUCTOR)
@interface MyAnnotation {}

// Somewhere in the code

class ExampleClass {
    @MyAnnotation
    ExampleClass() {
        // Code goes here
    }
}

ElementType.LOCAL_VARIABLE - Meetodi sisestel väljadel

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

@Target(ElementType.LOCAL_VARIABLE)
@interface MyAnnotation {}

// Somewhere in the code

class ExampleClass {
    void someMethod() {
        @MyAnnotation int value = 10;
        // Code goes here
    }
}

ElementType.ANNOTATION_TYPE - Saab teiste annotatsioonide koostamiseks kasutada

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

@Target(ElementType.ANNOTATION_TYPE)
@interface MyAnnotation {}

// Somewhere in the code

@MyAnnotation
@interface ExampleAnnotation {}

ElementType.PARAMETER - Meetodi parameetrite kohal

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

@Target(ElementType.PARAMETER)
@interface MyAnnotation {}

// Somewhere in the code

class ExampleClass {
    void someMethod(@MyAnnotation String input) {}
}

Mitme sihtmärgi deklaleerimiseks tuleb koostada massiiv

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

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})

@Retention

@Retention (ehk eluiga) kasutatakse, et määrata ära, millal antud märgendit kasutatakse ning millal see on saadaval.

Võimalikud väärtused on: SOURCE, CLASS, RUNTIME

RetentionPolicy.SOURCE - Kompilaator eirab antud märgendit

RetentionPolicy.CLASS - Märgend salvestatakse klassifaili kompilaatori poolt. Vaikeväärtus

RetentionPolicy.RUNTIME - Märgend salvestatakse klassifaili kompilaatori poolt ning on saadaval ka Java virtuaalmasinas. Reflektsiooniga võimalik hiljem ligi pääseda, sellest hiljem lähemalt.

Näide:

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

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {}

@Inherited

@Inherited märgib ära, et antud annotatsiooni päritakse automaatselt. Kui ülemklass kasutab koostatud märgendit, siis see kandub automaatselt üle ka alamklassidele

Näide:

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;

@Inherited
@Target(ElementType.TYPE)
@interface MyAnnotation {}

// Somewhere in the code

@MyAnnotation
class ExampleClassA {

}

// ExampleClassB inhertits @MyAnnotation from ExampleClassA, because we use @Inherited
class ExampleClassB extends ExampleClassA {

}

@Documented

@Documented annab märku, et antud annotatsioon peab olema dokumenteeritud.

Reflektsiooniga funktsionaalsuse lisamine

Kui annotatsioonile on elueaks märgitud RetentionPolicy.RUNTIME, siis sellele on võimalik juurde lisada ka funktsionaalsust, kasutades võtet nimega reflektsioon. Reflektsioon lubab meil juba käima pandud koodi lähemalt uurida ning vajadusel ka seda muutda.

Näite jaoks koostame väikse programmi, millega korrastame inimeste nimesid (Ainult esimene täht suureks, teised väikeseks).

Alustuseks loome annotatsiooni, mida saab ainult väljadel kasutada ning mida saaks ka runtime ajal kasutada:

import java.lang.annotation.*;

@Target(ElementType.FIELD)              // This annotation should only be used on fields
@Retention(RetentionPolicy.RUNTIME)     // This annotation can be used on runtime
@interface Pretify {}

Järgnevalt koostame klassi, kus sees hoiame inimese eesnime, perenime ning id. Kasutame seal sees oma koostatud märgendit:

class PersonData {
    private int id;
    @Pretify private String firstName;  // First name will be capitalized and corrected
    @Pretify private String lastName;   // Last name will be capitalized and corrected

    public PersonData(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "My name is: " + firstName + " " + lastName + ", id: " + id;
    }
}

Viimaseks kirjutame meetodi, mis otsib klassist üles väljad antud annotatsiooniga ning korrastab need ära. Antud võtte nimi on reflektsioon - analüüsime juba tööle pandud koodi ning muudame seda reaalajas. NB: Autor kirjutas meetodi Example klassi

import java.lang.reflect.Field;

public static void pretifyName(PersonData data) {
    Field[] fields = data.getClass().getDeclaredFields();               // Gets declared fields from class
    for (Field field : fields) {
        Pretify annotation = field.getAnnotation(Pretify.class);        // Tries to get annotation from field
        if (annotation == null) {                                       // If annotation wasn't found, continue to next field
            continue;
        }
        try {
            field.setAccessible(true);                                  // Make it so we can modify that field
            String value = field.get(data).toString().toLowerCase();    // Get value from field
            value = value.substring(0, 1)
                .toUpperCase() + value.substring(1);                    // Make first letter uppercase, others lowercase
            field.set(data, value);                                     // Set new value to field
            field.setAccessible(false);                                 // Make it back to private
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

Lõpuks koostame main meetodi ning kontrollime, kas töötab:

import java.util.Arrays;
import java.util.List;

public class Example {
    public static void main(String[] args) {
        List<PersonData> list = Arrays.asList(
                new PersonData(1, "persON", "oNe"),
                new PersonData(2, "nUmBEr", "TWO"),
                new PersonData(3, "thirD", "persoN")
        );
        list.forEach(Example::pretifyName);
        list.forEach(System.out::println);
    }

    // Previously written method somewhere here

}

Ning väljundiks saime korrastatud nimed:

My name is: Person One, id: 1
My name is: Number Two, id: 2
My name is: Third Person, id: 3

Lisaks soovitan ka korra muuta RetentionPolicy RUNTIME pealt millekski muuks ning võrrelda tulemusi.