JavaRush /Java-Blog /Random-DE /Java @Annotations. Was ist das und wie wird es verwendet?...
SemperAnte
Level 4
Донецк

Java @Annotations. Was ist das und wie wird es verwendet?

Veröffentlicht in der Gruppe Random-DE
Dieser Artikel richtet sich an Personen, die noch nie mit Annotations gearbeitet haben, aber gerne verstehen möchten, was es ist und womit es verwendet wird. Wenn Sie Erfahrung in diesem Bereich haben, glaube ich nicht, dass dieser Artikel Ihr Wissen irgendwie erweitern wird (und tatsächlich verfolge ich ein solches Ziel auch nicht). Außerdem ist der Artikel nicht für diejenigen geeignet, die gerade erst anfangen, die Java-Sprache zu lernen. Wenn Sie nicht verstehen, was eine Map<> oder HashMap<> ist , oder nicht wissen, was der Eintrag static{ } in einer Klassendefinition bedeutet, oder noch nie mit Reflektion gearbeitet haben, ist es für Sie zu früh, diesen Artikel zu lesen Versuchen Sie zu verstehen, was Anmerkungen sind. Dieses Tool selbst ist nicht für die Verwendung durch Anfänger gedacht, da es ein nicht ganz grundlegendes Verständnis der Interaktion von Klassen und Objekten erfordert (meiner Meinung nach) (danke an die Kommentare, die die Notwendigkeit dieses Postscripts aufgezeigt haben). Java @Annotations.  Was ist das und wie wird es verwendet?  - 1Also lasst uns anfangen. Anmerkungen in Java sind eine Art Beschriftung im Code, die die Metadaten für eine Funktion/Klasse/ein Paket beschreiben. Zum Beispiel die bekannte @Override-Annotation, die angibt, dass wir eine Methode der übergeordneten Klasse überschreiben werden. Ja, einerseits ist es auch ohne möglich, aber wenn die Eltern diese Methode nicht haben, besteht die Möglichkeit, dass wir den Code umsonst geschrieben haben, weil Diese spezielle Methode wird möglicherweise nie aufgerufen, aber mit der @Override-Annotation teilt uns der Compiler Folgendes mit: „Ich habe eine solche Methode in den Eltern nicht gefunden ... hier ist etwas schmutzig.“ Anmerkungen können jedoch mehr als nur die Bedeutung von „für Zuverlässigkeit“ haben: Sie können einige Daten speichern, die später verwendet werden.

Schauen wir uns zunächst die einfachsten Anmerkungen an, die von der Standardbibliothek bereitgestellt werden.

(Nochmals vielen Dank an die Kommentare. Zuerst dachte ich nicht, dass dieser Block benötigt wird.) Lassen Sie uns zunächst besprechen, was Anmerkungen sind. Jeder von ihnen hat zwei hauptsächlich erforderliche Parameter:
  • Speichertyp (Aufbewahrung);
  • Der Typ des Objekts, über das es angegeben wird (Ziel).

Speichertyp

Mit „Speichertyp“ meinen wir die Stufe, bis zu der unsere Anmerkung innerhalb der Klasse „überlebt“. Jede Annotation verfügt nur über einen der möglichen „Aufbewahrungstypen“, die in der RetentionPolicy- Klasse angegeben sind :
  • QUELLE – Die Anmerkung wird nur beim Schreiben von Code verwendet und vom Compiler ignoriert (d. h. sie wird nach der Kompilierung nicht gespeichert). Wird normalerweise für Präprozessoren (bedingt) oder Anweisungen an den Compiler verwendet
  • KLASSE – die Annotation bleibt nach der Kompilierung erhalten, wird aber von der JVM ignoriert (d. h. kann zur Laufzeit nicht verwendet werden). Wird normalerweise für Dienste von Drittanbietern verwendet, die Ihren Code als Plug-in-Anwendung laden
  • RUNTIME ist eine Annotation, die nach der Kompilierung gespeichert und von der JVM geladen wird (d. h. während der Ausführung des Programms selbst verwendet werden kann). Wird als Markierung im Code verwendet, die sich direkt auf die Ausführung des Programms auswirkt (ein Beispiel wird in diesem Artikel besprochen).

Der oben angegebene Objekttyp

Diese Beschreibung sollte fast wörtlich genommen werden, denn... In Java können Annotationen über alles angegeben werden (Felder, Klassen, Funktionen usw.) und für jede Annotation wird angegeben, worüber genau sie angegeben werden kann. Hier gibt es keine „Eine-Ding“-Regel mehr; über allem, was unten aufgeführt ist, kann eine Anmerkung angegeben werden, oder Sie können nur die notwendigen Elemente der ElementType- Klasse auswählen :
  • ANNOTATION_TYPE – eine weitere Anmerkung
  • CONSTRUCTOR – Klassenkonstruktor
  • FIELD – Klassenfeld
  • LOCAL_VARIABLE – lokale Variable
  • METHODE – Klassenmethode
  • PAKET – Beschreibung des Paketpakets
  • PARAMETER – Methodenparameter public void hello(@Annontation String param){}
  • TYP – oberhalb der Klasse angegeben
Insgesamt stellt uns die Standardsprachenbibliothek ab Java SE 1.8 10 Annotationen zur Verfügung. In diesem Artikel werden wir uns die häufigsten davon ansehen (wer interessiert sich für alle? Willkommen bei Javadoc ):

@Override

Aufbewahrung: QUELLE; Ziel: METHODE. Diese Annotation zeigt, dass die Methode, über die sie geschrieben wird, von der übergeordneten Klasse geerbt wird. Die erste Anmerkung, auf die jeder unerfahrene Java-Programmierer stößt, wenn er eine IDE verwendet, die diese @Override dauerhaft pusht. YouTube-Lehrer empfehlen oft entweder: „Löschen Sie es, damit es nicht stört“ oder: „Lassen Sie es, ohne sich zu fragen, warum es dort ist.“ Tatsächlich ist die Annotation mehr als nützlich: Sie ermöglicht Ihnen nicht nur zu verstehen, welche Methoden in dieser Klasse zum ersten Mal definiert wurden und welche die Eltern bereits haben (was zweifellos die Lesbarkeit Ihres Codes erhöht), sondern auch diese Annotation dient als „Selbstkontrolle“, dass Sie sich bei der Definition einer überladenen Funktion nicht geirrt haben.

@Veraltet

Aufbewahrung: Laufzeit; Ziel: KONSTRUKTOR, FELD, LOCAL_VARIABLE, METHODE, PAKET, PARAMETER, TYP. Diese Anmerkung identifiziert Methoden, Klassen oder Variablen, die „veraltet“ sind und in zukünftigen Versionen des Produkts möglicherweise entfernt werden. Diese Anmerkung wird normalerweise von denjenigen gefunden, die die Dokumentation von APIs oder derselben Standard-Java-Bibliothek lesen. Manchmal wird diese Anmerkung ignoriert, weil... es verursacht keine Fehler und beeinträchtigt das Leben grundsätzlich nicht wesentlich. Die Hauptbotschaft dieser Anmerkung lautet jedoch: „Wir haben eine bequemere Methode zum Implementieren dieser Funktionalität gefunden. Verwenden Sie sie, verwenden Sie nicht die alte.“ – Na ja, oder sonst – „Wir haben die Funktion umbenannt, aber diese.“ ist so, wir haben es dem Vermächtnis überlassen ...“ (was im Allgemeinen auch nicht schlecht ist). Kurz gesagt: Wenn Sie „@Deprecated“ sehen, ist es besser, nicht zu verwenden, was darüber hängt, es sei denn, es ist absolut notwendig, und es könnte sich lohnen, die Dokumentation noch einmal zu lesen, um zu verstehen, wie die vom veralteten Element ausgeführte Aufgabe jetzt implementiert wird. Anstelle der Verwendung von new Date().getYear() wird beispielsweise die Verwendung von Calendar.getInstance().get(Calendar.YEAR) empfohlen .

@SuppressWarnings

Aufbewahrung: QUELLE; Ziel: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE Diese Annotation deaktiviert die Ausgabe von Compiler-Warnungen, die das Element betreffen, über das sie angegeben ist. Ist die oben angegebene SOURCE-Anmerkung Felder, Methoden, Klassen?

@Zurückbehaltung

Aufbewahrung: RUNTIME; Ziel: ANNOTATION_TYPE; Diese Annotation gibt den „Speichertyp“ der Annotation an, über der sie angegeben ist. Ja, diese Anmerkung wird sogar für sich selbst verwendet ... Magie und das ist alles.

@Ziel

Aufbewahrung: RUNTIME; Ziel: ANNOTATION_TYPE; Diese Annotation gibt den Objekttyp an, über den die von uns erstellte Annotation angegeben werden kann. Ja, und es wird auch für Sie selbst verwendet, gewöhnen Sie sich daran ... Ich denke, hier können wir unsere Einführung in die Standardannotationen der Java-Bibliothek abschließen, denn Der Rest wird eher selten verwendet und obwohl sie ihre eigenen Vorteile haben, muss sich nicht jeder damit auseinandersetzen und ist völlig unnötig. Wenn Sie möchten, dass ich über eine bestimmte Anmerkung aus der Standardbibliothek spreche (oder vielleicht über Anmerkungen wie @NotNull und @Nullable, die nicht in der STL enthalten sind), schreiben Sie in die Kommentare – entweder freundliche Benutzer werden Ihnen dort antworten, oder Ich werde es tun, wenn ich es sehe. Wenn viele Leute nach einer Anmerkung fragen, werde ich diese auch dem Artikel hinzufügen.

Praktische Anwendung von RUNTIME-Annotationen

Eigentlich denke ich, dass das nun genug theoretisches Geschwätz ist: Kommen wir nun zur Praxis am Beispiel eines Bots. Nehmen wir an, Sie möchten einen Bot für ein soziales Netzwerk schreiben. Alle großen Netzwerke wie VK, Facebook und Discord verfügen über eigene APIs, mit denen Sie einen Bot schreiben können. Für dieselben Netzwerke gibt es bereits geschriebene Bibliotheken für die Arbeit mit APIs, auch in Java. Daher werden wir uns nicht mit der Arbeit einer API oder Bibliothek befassen. Alles, was wir in diesem Beispiel wissen müssen, ist, dass unser Bot auf Nachrichten antworten kann, die an den Chat gesendet werden, in dem sich unser Bot tatsächlich befindet. Nehmen wir an, wir haben eine MessageListener- Klasse mit einer Funktion:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Es ist für die Verarbeitung der empfangenen Nachricht verantwortlich. Alles, was wir von der MessageReceivedEvent- Klasse benötigen, ist die Zeichenfolge der empfangenen Nachricht (z. B. „Hallo“ oder „Bot, hallo“). Es ist eine Überlegung wert: In verschiedenen Bibliotheken werden diese Klassen unterschiedlich aufgerufen. Ich habe die Bibliothek für Discord genutzt. Deshalb möchten wir, dass der Bot auf einige Befehle reagiert, die mit „Bot“ beginnen (mit oder ohne Komma – entscheiden Sie selbst: Aus Gründen der Lektion gehen wir davon aus, dass dort kein Komma stehen sollte). Das heißt, unsere Funktion beginnt mit etwas wie:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
Und jetzt haben wir viele Möglichkeiten, diesen oder jenen Befehl umzusetzen. Zweifellos müssen Sie zuerst den Befehl von seinen Argumenten trennen, das heißt, ihn in ein Array aufteilen.
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);
            //Получoder command = "(команда)"; nArgs = {"аргумент1", "аргумент2",..."аргументN"};
            //Данный массив может быть пустым
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            //Вывод списка команд oder Wieого-либо Mitteilungen
            //В случае если просто написать "Бот"
        }
    }
}
An diesem Codeteil kommen wir nicht vorbei, da es immer notwendig ist, den Befehl von den Argumenten zu trennen. Aber dann haben wir die Wahl:
  • Führen Sie Folgendes aus: if(command.equalsIngnoreCase("..."))
  • Wechseln (Befehl)
  • Machen Sie eine andere Art der Verarbeitung ...
  • Oder greifen Sie auf die Hilfe von Annotations zurück.
Und nun sind wir endlich beim praktischen Teil der Verwendung von Anmerkungen angelangt. Schauen wir uns den Anmerkungscode für unsere Aufgabe an (er kann natürlich abweichen).
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

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

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

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

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

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

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

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

}
Wichtig! Jeder Parameter wird als Funktion (mit Klammern) beschrieben. Als Parameter können nur Grundelemente, String und Enum verwendet werden . Sie können List<String> args(); nicht schreiben . - Fehler. Nachdem wir nun die Annotation beschrieben haben, erstellen wir eine Klasse und nennen sie 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();

    }
}
Eine kleine Unannehmlichkeit ist erwähnenswert: t.c. Wir kämpfen jetzt für die Universalität, alle Funktionen müssen die gleiche Liste formaler Parameter haben, also muss die Funktion auch dann einen String[] args- Parameter haben, wenn der Befehl keine Argumente hat . Wir haben nun 3 Befehle beschrieben: Hallo, Tschüss, Hilfe. Jetzt ändern wir unseren MessageListener , um etwas damit zu tun. Aus Gründen der Bequemlichkeit und Geschwindigkeit der Arbeit speichern wir unsere Befehle sofort in HashMap :
public class MessageListner
{
    //Map который хранит Wie ключ команду
    //А Wie Bedeutung функцию которая будет обрабатывать команду
    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))
            {
                //Берем ein Objekt нашей Аннотации
                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)
            {
                //Вывод списка команд oder Wieого-либо Mitteilungen
                //В случае если просто написать "Бот"
            }
        }
    }
}
Das ist alles, was unsere Teams zum Funktionieren benötigen. Das Hinzufügen eines neuen Befehls ist nun kein neues Wenn, kein neuer Fall, in dem die Anzahl der Argumente neu berechnet werden müsste und auch die Hilfe neu geschrieben werden müsste, indem neue Zeilen hinzugefügt würden. Um nun einen Befehl hinzuzufügen, müssen wir nur noch eine neue Funktion mit der Annotation @Command in der CommandListener-Klasse hinzufügen und das war’s – der Befehl wird hinzugefügt, Fälle werden berücksichtigt, Hilfe wird automatisch hinzugefügt. Es ist absolut unbestreitbar, dass dieses Problem auf viele andere Arten gelöst werden kann. Ja, alles, was mit Hilfe von Annotationen/Reflexionen gemacht werden kann, kann auch ohne sie gemacht werden. Die einzige Frage ist Bequemlichkeit, Optimalität und Codegröße, natürlich das Anbringen einer Annotation überall dort, wo es den geringsten Hinweis darauf gibt, dass sie verwendet werden kann Es ist auch nicht die rationalste Option, bei allem müssen Sie wissen, wann Sie aufhören müssen =). Aber beim Schreiben von APIs, Bibliotheken oder Programmen, in denen es möglich ist, denselben Codetyp (aber nicht genau denselben) zu wiederholen, sind Annotationen zweifellos die optimale Lösung.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION