JavaRush /Java Blog /Random-IT /Java @Annotazioni. Cos'è e come usarlo?
SemperAnte
Livello 4
Донецк

Java @Annotazioni. Cos'è e come usarlo?

Pubblicato nel gruppo Random-IT
Questo articolo è destinato a persone che non hanno mai lavorato con Annotazioni, ma vorrebbero capire cos'è e a cosa serve. Se hai esperienza in questo settore, non credo che questo articolo amplierà in qualche modo le tue conoscenze (e, in effetti, non perseguo tale obiettivo). Inoltre, l'articolo non è adatto a coloro che stanno appena iniziando a imparare il linguaggio Java. Se non capisci cos'è una Map<> o una HashMap<> o non sai cosa significa la voce static{ } all'interno di una definizione di classe, o non hai mai lavorato con la riflessione, è troppo presto per leggere questo articolo e cercare di capire cosa sono le annotazioni. Questo strumento in sé non è stato creato per essere utilizzato dai principianti, poiché richiede una comprensione non del tutto basilare dell'interazione di classi e oggetti (la mia opinione) (grazie ai commenti per aver mostrato la necessità di questo postscript). Java @Annotazioni.  Cos'è e come usarlo?  -1Quindi iniziamo. Le annotazioni in Java sono una sorta di etichette nel codice che descrivono i metadati per una funzione/classe/pacchetto. Ad esempio, la famosa @Override Annotation, che indica che sovrascriveremo un metodo della classe genitore. Sì, da un lato è possibile senza di esso, ma se i genitori non hanno questo metodo, c'è la possibilità che abbiamo scritto il codice invano, perché Questo particolare metodo potrebbe non essere mai chiamato, ma con l'annotazione @Override il compilatore ci dirà che: "Non ho trovato un metodo del genere nei genitori... c'è qualcosa di sporco qui." Tuttavia, le annotazioni possono avere più del semplice significato di “affidabilità”: possono memorizzare alcuni dati che verranno utilizzati in seguito.

Innanzitutto, diamo un'occhiata alle annotazioni più semplici fornite dalla libreria standard.

(grazie ancora ai commenti, all'inizio non pensavo che questo blocco fosse necessario) Innanzitutto, parliamo di cosa sono le annotazioni. Ognuno di essi ha 2 principali parametri richiesti :
  • Tipo di archiviazione (Conservazione);
  • Il tipo di oggetto su cui è indicato (Target).

Tipo di archiviazione

Per “tipo di archiviazione” intendiamo la fase alla quale la nostra annotazione “sopravvive” all'interno della classe. Ogni annotazione ha solo uno dei possibili "tipi di conservazione" specificati nella classe RetentionPolicy :
  • FONTE : l'annotazione viene utilizzata solo durante la scrittura del codice e viene ignorata dal compilatore (ovvero non viene salvata dopo la compilazione). Tipicamente utilizzato per qualsiasi preprocessore (condizionatamente) o istruzioni per il compilatore
  • CLASS : l'annotazione viene conservata dopo la compilazione, ma viene ignorata dalla JVM (ovvero non può essere utilizzata in fase di runtime). In genere utilizzato per servizi di terze parti che caricano il codice come applicazione plug-in
  • RUNTIME è un'annotazione che viene salvata dopo la compilazione e caricata dalla JVM (può cioè essere utilizzata durante l'esecuzione del programma stesso). Utilizzati come segni nel codice che influiscono direttamente sull'esecuzione del programma (un esempio verrà discusso in questo articolo)

Il tipo di oggetto sopra il quale è indicato

Questa descrizione va presa quasi alla lettera perché... in Java le annotazioni possono essere specificate su qualsiasi cosa (campi, classi, funzioni, ecc.) e per ogni annotazione viene indicato su cosa esattamente può essere specificata. Non esiste più una regola "una cosa" qui; è possibile specificare un'annotazione sopra tutto ciò che è elencato di seguito oppure è possibile selezionare solo gli elementi necessari della classe ElementType :
  • ANNOTATION_TYPE : un'altra annotazione
  • COSTRUTTORE : costruttore della classe
  • FIELD - campo della classe
  • LOCAL_VARIABLE - variabile locale
  • METODO - metodo della classe
  • PACCHETTO - descrizione del pacchetto pacchetto
  • PARAMETRO - parametro del metodo public void hello(@Annontation String param){}
  • TIPO - indicato sopra la classe
In totale, a partire da Java SE 1.8, la libreria della lingua standard ci fornisce 10 annotazioni. In questo articolo ne esamineremo i più comuni (chi è interessato a tutti? Benvenuto in Javadoc ):

@Oltrepassare

Conservazione: FONTE; Obiettivo: METODO. Questa annotazione mostra che il metodo su cui è scritto è ereditato dalla classe genitore. La prima annotazione che ogni programmatore Java alle prime armi incontra quando utilizza un IDE che spinge persistentemente questi @Override. Spesso gli insegnanti di YouTube consigliano: “cancellalo in modo che non interferisca” oppure: “lascialo senza chiederti perché è lì”. In effetti, l'annotazione è più che utile: non solo ti permette di capire quali metodi sono stati definiti per la prima volta in questa classe, e quali invece hanno già i genitori (il che aumenta senza dubbio la leggibilità del tuo codice), ma anche questa annotazione serve come un "autocontrollo" per verificare che non ti sei sbagliato nel definire una funzione sovraccarica.

@Deprecato

Conservazione: tempo di esecuzione; Obiettivo: COSTRUTTORE, CAMPO, VARIABILE_LOCALE, METODO, PACCHETTO, PARAMETRO, TIPO. Questa annotazione identifica metodi, classi o variabili che sono "obsoleti" e potrebbero essere rimossi nelle versioni future del prodotto. Questa annotazione viene solitamente riscontrata da coloro che leggono la documentazione di qualsiasi API o della stessa libreria Java standard. A volte questa annotazione viene ignorata perché... non causa errori e, in linea di principio, di per sé non interferisce molto con la vita. Tuttavia, il messaggio principale che porta questa annotazione è "abbiamo trovato un metodo più conveniente per implementare questa funzionalità, usatelo, non usate quello vecchio" - beh, oppure - "abbiamo rinominato la funzione, ma questa è così, l'abbiamo lasciato in eredità...” (anche questo in generale non è male). In breve, se vedi @Deprecated, è meglio cercare di non utilizzare ciò su cui pende a meno che non sia assolutamente necessario, e potrebbe valere la pena rileggere la documentazione per capire come viene ora implementata l'attività eseguita dall'elemento deprecato. Ad esempio, invece di utilizzare new Date().getYear() si consiglia di utilizzare Calendar.getInstance().get(Calendar.YEAR) .

@SuppressWarnings

Conservazione: FONTE; Destinazione: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE Questa annotazione disabilita l'output degli avvisi del compilatore che riguardano l'elemento su cui è specificato. L'annotazione SOURCE è indicata sopra campi, metodi, classi.

@Ritenzione

Conservazione: RUNTIME; Destinazione: ANNOTATION_TYPE; Questa annotazione specifica il "tipo di archiviazione" dell'annotazione sopra la quale è specificata. Sì, questa annotazione serve anche per se stessa... magia e basta.

@Bersaglio

Conservazione: RUNTIME; Destinazione: ANNOTATION_TYPE; Questa annotazione specifica il tipo di oggetto su cui può essere indicata l'annotazione che creiamo. Sì, e serve anche per te stesso, abituati... Penso che qui possiamo completare la nostra introduzione alle annotazioni standard della libreria Java, perché il resto viene utilizzato abbastanza raramente e, sebbene abbiano i loro vantaggi, non tutti devono affrontarli e sono completamente inutili. Se vuoi che parli di un'annotazione specifica della libreria standard (o, forse, di annotazioni come @NotNull e @Nullable, che non sono incluse nell'STL), scrivi nei commenti: gli utenti gentili ti risponderanno lì, oppure Lo farò quando lo vedrò. Se molte persone chiedono qualche tipo di annotazione, la aggiungerò anche all'articolo.

Applicazione pratica delle annotazioni RUNTIME

In realtà penso che chiacchiere teoriche siano sufficienti: passiamo alla pratica usando l’esempio di un bot. Diciamo che vuoi scrivere un bot per qualche social network. Tutte le principali reti, come VK, Facebook, Discord, hanno le proprie API che ti consentono di scrivere un bot. Per queste stesse reti esistono già librerie scritte per lavorare con le API, anche in Java. Pertanto, non approfondiremo il lavoro di alcuna API o libreria. Tutto quello che dobbiamo sapere in questo esempio è che il nostro bot può rispondere ai messaggi inviati alla chat in cui si trova effettivamente il nostro bot. Cioè, diciamo di avere una classe MessageListener con una funzione:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
È responsabile dell'elaborazione del messaggio ricevuto. Tutto ciò di cui abbiamo bisogno dalla classe MessageReceivedEvent è la stringa del messaggio ricevuto (ad esempio “Hello” o “Bot, hello”). Vale la pena considerare: in diverse biblioteche queste classi sono chiamate diversamente. Ho usato la libreria per Discord. E quindi vogliamo fare in modo che il bot reagisca ad alcuni comandi che iniziano con "Bot" (con o senza virgola - decidi tu stesso: per il bene di questa lezione, supporremo che non dovrebbe esserci una virgola). Cioè, la nostra funzione inizierà con qualcosa del tipo:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
E ora abbiamo molte opzioni per implementare questo o quel comando. Indubbiamente, prima devi separare il comando dai suoi argomenti, cioè dividerlo in un array.
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {
        try
        {
            //получим массив {"Бот", "(команду)", "аргумент1", "аргумент2",... "аргументN"};
            String[] args = message.split(" ");
            //Для удобства уберем "бот" и отделим команду от аргументов
            String command = args[1];
            String[] nArgs = Arrays.copyOfRange(args, 2, args.length);
            //Получor command = "(команда)"; nArgs = {"аргумент1", "аргумент2",..."аргументN"};
            //Данный массив может быть пустым
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            //Вывод списка команд or Howого-либо messages
            //В случае если просто написать "Бот"
        }
    }
}
Non c'è modo di evitare questo pezzo di codice, perché è sempre necessario separare il comando dagli argomenti. Ma poi abbiamo una scelta:
  • Esegui if(command.equalsIngnoreCase("..."))
  • Esegui il cambio (comando)
  • Esegui un altro modo di elaborazione...
  • Oppure ricorrere all'aiuto delle Annotazioni.
E ora siamo finalmente arrivati ​​alla parte pratica dell'utilizzo delle Annotazioni. Diamo un'occhiata al codice di annotazione per la nostra attività (potrebbe differire, ovviamente).
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//Указывает, что наша Аннотация может быть использована
//Во время выполнения через Reflection (нам How раз это нужно).
@Retention(RetentionPolicy.RUNTIME)

//Указывает, что целью нашей Аннотации является метод
//Не класс, не переменная, не поле, а именно метод.
@Target(ElementType.METHOD)
public @interface Command //Описание. Заметим, что перед interface стоит @;
{
    //Команда за которую будет отвечать функция (например "привет");
    String name();

     //Аргументы команды, использоваться будут для вывода списка команд
    String args();

     //Минимальное количество аргументов, сразу присвоor 0 (логично)
    int minArgs() default 0;

    //Описание, тоже для списка
    String desc();

     //Максимальное число аргументов. В целом не обязательно, но тоже можно использовать
    int maxArgs() default Integer.MAX_VALUE;

     //Показывать ли команду в списке (вовсе необязательная строка, но мало ли, пригодится!)
    boolean showInHelp() default true;

    //Какие команды будут считаться эквивалентными нашей
    //(Например для "привет", это может быть "Здаров", "Прив" и т.д.)
    //Под каждый случай заводить функцию - не рационально
    String[] aliases();

}
Importante! Ogni parametro è descritto come una funzione (tra parentesi). Solo le primitive, String , Enum possono essere utilizzate come parametri . Non puoi scrivere List<String> args(); - errore. Ora che abbiamo descritto l'annotazione, creiamo una classe, chiamiamola CommandListener .
public class CommandListener
{
    @Command(name = "привет",
            args = "",
            desc = "Будь культурным, поздоровайся",
            showInHelp = false,
            aliases = {"здаров"})
    public void hello(String[] args)
    {
        //Какой-то функционал, на Ваше усмотрение.
    }

    @Command(name = "пока",
            args = "",
            desc = "",
            aliases = {"удачи"})
    public void bye(String[] args)
    {
         // Функционал
    }

    @Command(name = "помощь",
            args = "",
            desc = "Выводит список команд",
            aliases = {"help", "команды"})
    public void help(String[] args)
    {
        StringBuilder sb = new StringBuilder("Список команд: \n");
        for (Method m : this.getClass().getDeclaredMethods())
        {
            if (m.isAnnotationPresent(Command.class))
            {
                Command com = m.getAnnotation(Command.class);
                if (com.showInHelp()) //Если нужно показывать команду в списке.
                {
                    sb.append("Бот, ")
                       .append(com.name()).append(" ")
                       .append(com.args()).append(" - ")
                       .append(com.desc()).append("\n");
                }
            }
        }
        //Отправка sb.toString();

    }
}
Da segnalare un piccolo inconveniente: t.c. Ora stiamo lottando per l'universalità, tutte le funzioni devono avere lo stesso elenco di parametri formali, quindi anche se il comando non ha argomenti, la funzione deve avere un parametro String[] args . Abbiamo ora descritto 3 comandi: ciao, ciao, aiuto. Ora modifichiamo il nostro MessageListener per fare qualcosa con questo. Per comodità e velocità di lavoro, memorizzeremo immediatamente i nostri comandi in HashMap :
public class MessageListner
{
    //Map который хранит How ключ команду
    //А How meaning функцию которая будет обрабатывать команду
    private static final Map<String, Method> COMMANDS = new HashMap<>();

    //Объект класса с командами (по сути нужен нам для рефлексии)
    private static final CommandListener LISTENER = new CommandListener();

    static
    {
       //Берем список всех методов в классе CommandListener
        for (Method m : LISTENER.getClass().getDeclaredMethods())
        {
            //Смотрим, есть ли у метода нужная нам Аннотация @Command
            if (m.isAnnotationPresent(Command.class))
            {
                //Берем an object нашей Аннотации
                Command cmd = m.getAnnotation(Command.class);
                //Кладем в качестве ключа нашей карты параметр name()
                //Определенный у нашей аннотации,
                //m — переменная, хранящая наш метод
                COMMANDS.put(cmd.name(), m);

                //Также заносим каждый элемент aliases
               //Как ключ указывающий на тот же самый метод.
                for (String s : cmd.aliases())
                {
                    COMMANDS.put(s, m);
                }
            }
        }
    }

    public void onMessageReceived(MessageReceivedEvent event)
    {

        String message = event.getMessage().toLowerCase();
        if (message.startsWith("бот"))
        {
            try
            {
                String[] args = message.split(" ");
                String command = args[1];
                String[] nArgs = Arrays.copyOfRange(args, 2, args.length);
                Method m = COMMANDS.get(command);
                if (m == null)
                {
                    //(вывод помощи)
                    return;
                }
                Command com = m.getAnnotation(Command.class);
                if (nArgs.length < com.minArgs())
                {
                    //что-то если аргументов меньше чем нужно
                }
                else if (nArgs.length > com.maxArgs())
                {
                    //что-то если аргументов больше чем нужно
                }
                //Через рефлексию вызываем нашу функцию-обработчик
                //Именно потому что мы всегда передаем nArgs у функции должен быть параметр
                //String[] args — иначе она просто не будет найдена;
                m.invoke(LISTENER, nArgs);
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
                //Вывод списка команд or Howого-либо messages
                //В случае если просто написать "Бот"
            }
        }
    }
}
Questo è tutto ciò che serve affinché i nostri team funzionino. Ora, l'aggiunta di un nuovo comando non è un nuovo se, non un nuovo caso, in cui il numero di argomenti dovrebbe essere ricalcolato e anche l'aiuto dovrebbe essere riscritto, aggiungendovi nuove righe. Ora, per aggiungere un comando, dobbiamo solo aggiungere una nuova funzione con l'annotazione @Command nella classe CommandListener e il gioco è fatto: il comando viene aggiunto, i casi vengono presi in considerazione, la guida viene aggiunta automaticamente. È assolutamente indiscutibile che questo problema può essere risolto in molti altri modi. Sì, tutto ciò che può essere fatto con l'aiuto di annotazioni/riflessioni può essere fatto senza di esse, l'unica domanda è la comodità, l'ottimalità e la dimensione del codice, ovviamente, attaccando un'annotazione ovunque ci sia il minimo accenno che sarà possibile utilizzare inoltre non è l'opzione più razionale, in ogni caso devi sapere quando fermarti =). Ma quando si scrivono API, Librerie o programmi in cui è possibile ripetere lo stesso tipo (ma non esattamente lo stesso) codice, le annotazioni sono senza dubbio la soluzione ottimale.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION