JavaRush /Java Blog /Random-TL /Java @Mga Anotasyon. Ano ito at paano ito gamitin?
SemperAnte
Antas
Донецк

Java @Mga Anotasyon. Ano ito at paano ito gamitin?

Nai-publish sa grupo
Ang artikulong ito ay inilaan para sa mga taong hindi kailanman nagtrabaho sa Mga Anotasyon, ngunit gustong maunawaan kung ano ito at kung saan ito ginagamit. Kung mayroon kang karanasan sa lugar na ito, sa palagay ko ay hindi mapapalawak ng artikulong ito ang iyong kaalaman (at, sa katunayan, hindi ko hinahabol ang gayong layunin). Gayundin, ang artikulo ay hindi angkop para sa mga nagsisimula pa lamang matuto ng wikang Java. Kung hindi mo naiintindihan kung ano ang isang Map<> o HashMap<> o hindi mo alam kung ano ang ibig sabihin ng static{ } entry sa loob ng isang class definition, o hindi kailanman gumana nang may pagmuni-muni, masyadong maaga para sa iyo na basahin ang artikulong ito at subukan mong unawain kung ano ang anotasyon. Ang tool na ito mismo ay hindi nilikha para sa paggamit ng mga nagsisimula, dahil nangangailangan ito ng hindi ganap na pangunahing pag-unawa sa pakikipag-ugnayan ng mga klase at bagay (sa aking opinyon) (salamat sa mga komento para sa pagpapakita ng pangangailangan para sa postscript na ito). Java @Mga Anotasyon.  Ano ito at paano ito gamitin?  - 1Kaya simulan na natin. Ang mga anotasyon sa Java ay isang uri ng mga label sa code na naglalarawan sa metadata para sa isang function/class/package. Halimbawa, ang kilalang @Override Annotation, na nagpapahiwatig na i-o-override natin ang isang paraan ng parent class. Oo, sa isang banda, posible kung wala ito, ngunit kung ang mga magulang ay walang pamamaraang ito, may posibilidad na isinulat namin ang code nang walang kabuluhan, dahil Maaaring hindi kailanman matatawag ang partikular na pamamaraang ito, ngunit sa @Override Annotation sasabihin sa amin ng compiler na: "Hindi ako nakahanap ng ganoong paraan sa mga magulang... may marumi dito." Gayunpaman, ang mga Anotasyon ay maaaring magdala ng higit pa sa kahulugan ng "para sa pagiging maaasahan": maaari silang mag-imbak ng ilang data na gagamitin sa ibang pagkakataon.

Una, tingnan natin ang pinakasimpleng anotasyon na ibinigay ng karaniwang library.

(salamat muli sa mga komento, noong una ay hindi ko naisip na kailangan ang block na ito) Una, talakayin natin kung ano ang mga anotasyon. Ang bawat isa sa kanila ay may 2 pangunahing kinakailangang mga parameter:
  • Uri ng imbakan (Retention);
  • Ang uri ng bagay kung saan ito ipinahiwatig (Target).

Uri ng imbakan

Sa pamamagitan ng "uri ng storage" ang ibig naming sabihin ay ang yugto kung saan ang aming anotasyon ay "nakaligtas" sa loob ng klase. Ang bawat anotasyon ay may isa lamang sa mga posibleng "uri ng pagpapanatili" na tinukoy sa klase ng RetentionPolicy :
  • SOURCE - ginagamit lang ang anotasyon kapag nagsusulat ng code at hindi pinapansin ng compiler (i.e., hindi ito nai-save pagkatapos ng compilation). Karaniwang ginagamit para sa anumang mga preprocessor (may kondisyon), o mga tagubilin sa compiler
  • CLASS - ang anotasyon ay pinapanatili pagkatapos ng compilation, ngunit binabalewala ng JVM (i.e. hindi magagamit sa runtime). Karaniwang ginagamit para sa anumang mga serbisyo ng third-party na naglo-load ng iyong code bilang isang plug-in na application
  • Ang RUNTIME ay isang anotasyon na nai-save pagkatapos ng compilation at na-load ng JVM (ibig sabihin, maaaring magamit sa panahon ng pagpapatupad ng program mismo). Ginamit bilang mga marka sa code na direktang nakakaapekto sa pagpapatupad ng programa (isang halimbawa ang tatalakayin sa artikulong ito)

Ang uri ng bagay sa itaas na ipinahiwatig

Ang paglalarawang ito ay dapat kunin nang halos literal, dahil... sa Java, ang mga anotasyon ay maaaring tukuyin sa anumang bagay (Mga Patlang, mga klase, mga pag-andar, atbp.) at para sa bawat anotasyon ito ay ipinahiwatig kung ano ang eksaktong maaari itong tukuyin. Wala nang "isang bagay" na panuntunan dito; maaaring tukuyin ang isang anotasyon sa itaas ng lahat ng nakalista sa ibaba, o maaari mo lamang piliin ang mga kinakailangang elemento ng klase ng ElementType :
  • ANNOTATION_TYPE - isa pang anotasyon
  • CONSTRUCTOR - tagapagbuo ng klase
  • FIELD - field ng klase
  • LOCAL_VARIABLE - lokal na variable
  • PARAAN - paraan ng klase
  • PACKAGE - paglalarawan ng package package
  • PARAMETER - method parameter public void hello(@Annontation String param){}
  • URI - nakasaad sa itaas ng klase
Sa kabuuan, mula sa Java SE 1.8, ang karaniwang library ng wika ay nagbibigay sa amin ng 10 anotasyon. Sa artikulong ito titingnan natin ang pinakakaraniwan sa kanila (sino ang interesado sa kanilang lahat? Maligayang pagdating sa Javadoc ):

@I-override

Pagpapanatili: SOURCE; Target: PARAAN. Ipinapakita ng anotasyong ito na ang paraan kung saan ito nakasulat ay minana mula sa parent class. Ang unang anotasyon na makikita ng bawat baguhang Java programmer kapag gumagamit ng IDE na patuloy na nagtutulak sa mga @Override na ito. Kadalasan, inirerekomenda ng mga guro mula sa YouTube ang alinman sa: "burahin ito para hindi ito makagambala," o: "iwanan ito nang hindi iniisip kung bakit nandoon." Sa katunayan, ang anotasyon ay higit pa sa kapaki-pakinabang: hindi lamang ito nagbibigay-daan sa iyo na maunawaan kung aling mga pamamaraan ang tinukoy sa klase na ito sa unang pagkakataon, at kung saan ang mga magulang ay mayroon na (na walang alinlangan na nagpapataas ng pagiging madaling mabasa ng iyong code), kundi pati na rin ang anotasyong ito. nagsisilbing "self-check" na hindi ka nagkamali kapag tinukoy ang isang overloaded na function.

@Hindi na ginagamit

Pagpapanatili: Runtime; Target: CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE. Tinutukoy ng anotasyong ito ang mga pamamaraan, klase o variable na "hindi na ginagamit" at maaaring alisin sa mga susunod na bersyon ng produkto. Ang anotasyong ito ay kadalasang nakikita ng mga nagbabasa ng dokumentasyon ng anumang mga API, o ng parehong karaniwang Java library. Minsan binabalewala ang anotasyong ito dahil... hindi ito nagiging sanhi ng anumang mga pagkakamali at, sa prinsipyo, sa sarili nito ay hindi gaanong nakakasagabal sa buhay. Gayunpaman, ang pangunahing mensahe na dinadala ng anotasyong ito ay "nakagawa kami ng isang mas maginhawang paraan para sa pagpapatupad ng pag-andar na ito, gamitin ito, huwag gamitin ang luma" - mabuti, o kung hindi - "pinalitan namin ang pangalan ng function, ngunit ito is so, we left it for legacy...” (which is also usually not bad). Sa madaling salita, kung nakikita mo ang @Deprecated, mas mainam na subukang huwag gamitin ang nakabitin maliban na lang kung ito ay talagang kinakailangan, at maaaring sulit na basahin muli ang dokumentasyon upang maunawaan kung paano ipinapatupad na ngayon ang gawaing ginagampanan ng hindi na ginagamit na elemento. Halimbawa, sa halip na gumamit ng bagong Date().getYear() inirerekomendang gamitin ang Calendar.getInstance().get(Calendar.YEAR) .

@SuppressWarnings

Pagpapanatili: SOURCE; Target: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE Hindi pinapagana ng anotasyong ito ang output ng mga babala ng compiler na may kinalaman sa elemento kung saan ito tinukoy. Ang SOURCE annotation ba ay nakasaad sa itaas ng mga field, method, classes.

@Pagpapanatili

Pagpapanatili: RUNTIME; Target: ANNOTATION_TYPE; Tinutukoy ng anotasyong ito ang "uri ng imbakan" ng anotasyon sa itaas kung saan ito tinukoy. Oo, ginagamit ang anotasyong ito kahit para sa sarili nito... magic at iyon lang.

@Target

Pagpapanatili: RUNTIME; Target: ANNOTATION_TYPE; Tinukoy ng anotasyong ito ang uri ng bagay kung saan maaaring ipahiwatig ang anotasyong ginawa namin. Oo, at ginagamit din ito para sa iyong sarili, masanay ka na... Sa palagay ko dito natin matatapos ang ating pagpapakilala sa karaniwang mga anotasyon ng Java library, dahil ang iba ay bihirang ginagamit at, kahit na mayroon silang sariling mga benepisyo, hindi lahat ay kailangang harapin ang mga ito at ganap na hindi kinakailangan. Kung gusto mong pag-usapan ko ang isang partikular na anotasyon mula sa karaniwang aklatan (o, marahil, mga anotasyon tulad ng @NotNull at @Nullable, na hindi kasama sa STL), sumulat sa mga komento - alinman sa mga mabait na gumagamit ay sasagutin ka doon, o gagawin ko kapag nakita ko ito. Kung maraming tao ang humingi ng ilang uri ng anotasyon, idaragdag ko rin ito sa artikulo.

Praktikal na aplikasyon ng RUNTIME annotation

Sa totoo lang, sa tingin ko ay sapat na ang teoretikal na satsat: magpatuloy tayo sa pagsasanay gamit ang halimbawa ng bot. Sabihin nating gusto mong magsulat ng bot para sa ilang social network. Ang lahat ng mga pangunahing network, tulad ng VK, Facebook, Discord, ay may sariling mga API na nagbibigay-daan sa iyong magsulat ng bot. Para sa parehong mga network na ito, mayroon nang nakasulat na mga aklatan para sa pagtatrabaho sa mga API, kabilang ang Java. Samakatuwid, hindi namin susuriin ang gawain ng anumang API o library. Ang kailangan lang nating malaman sa halimbawang ito ay maaaring tumugon ang ating bot sa mga mensaheng ipinadala sa chat kung saan aktwal na matatagpuan ang ating bot. Iyon ay, sabihin nating mayroon tayong klase ng MessageListener na may function:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Responsable ito sa pagproseso ng natanggap na mensahe. Ang kailangan lang namin mula sa klase ng MessageReceivedEvent ay ang string ng natanggap na mensahe (halimbawa, "Hello" o "Bot, hello"). Ito ay nagkakahalaga ng pagsasaalang-alang: sa iba't ibang mga aklatan ang mga klase na ito ay tinatawag na iba. Ginamit ko ang library para sa Discord. At kaya gusto naming gawing reaksyon ang bot sa ilang mga utos na nagsisimula sa "Bot" (mayroon o walang kuwit - magpasya para sa iyong sarili: para sa kapakanan ng araling ito, ipagpalagay namin na hindi dapat magkaroon ng kuwit doon). Iyon ay, ang aming function ay magsisimula sa isang bagay tulad ng:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
At ngayon mayroon kaming maraming mga pagpipilian para sa pagpapatupad ng ito o ang utos na iyon. Walang alinlangan, kailangan mo munang paghiwalayin ang utos mula sa mga argumento nito, iyon ay, hatiin ito sa isang 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
            //В случае если просто написать "Бот"
        }
    }
}
Walang paraan na maiiwasan natin ang piraso ng code na ito, dahil ang paghihiwalay ng command mula sa mga argumento ay palaging kinakailangan. Ngunit pagkatapos ay mayroon tayong pagpipilian:
  • Gawin kung(command.equalsIngnoreCase("..."))
  • Lumipat (utos)
  • Gumawa ng ibang paraan ng pagproseso...
  • O gumamit ng tulong ng Mga Anotasyon.
At ngayon, naabot na namin sa wakas ang praktikal na bahagi ng paggamit ng Mga Anotasyon. Tingnan natin ang annotation code para sa ating gawain (maaaring magkaiba ito, siyempre).
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();

}
Mahalaga! Ang bawat parameter ay inilalarawan bilang isang function (na may mga panaklong). Primitives lamang, String , Enum ang maaaring gamitin bilang mga parameter . Hindi mo maaaring isulat ang List<String> args(); - pagkakamali. Ngayong inilarawan na natin ang Anotasyon, gumawa tayo ng klase, tawagan itong 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();

    }
}
Ito ay nagkakahalaga ng pagpuna sa isang maliit na abala: t.c. Pinaglalaban natin ngayon ang pagiging pandaigdigan, ang lahat ng mga function ay dapat magkaroon ng parehong listahan ng mga pormal na parameter, kaya kahit na ang command ay walang mga argumento, ang function ay dapat magkaroon ng isang String[] args parameter . Inilarawan na namin ngayon ang 3 utos: hello, bye, help. Ngayon, baguhin natin ang ating MessageListener para magawa ito. Para sa kaginhawahan at bilis ng trabaho, agad naming iimbak ang aming mga utos sa 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
                //В случае если просто написать "Бот"
            }
        }
    }
}
Iyon lang ang kailangan para gumana ang aming mga koponan. Ngayon, ang pagdaragdag ng bagong utos ay hindi bago kung, hindi isang bagong kaso, kung saan ang bilang ng mga argumento ay kailangang muling kalkulahin, at ang tulong ay kailangan ding muling isulat, pagdaragdag ng mga bagong linya dito. Ngayon, para magdagdag ng command, kailangan lang naming magdagdag ng bagong function na may @Command annotation sa CommandListener class at iyon lang - idinagdag ang command, isinasaalang-alang ang mga kaso, awtomatikong idinagdag ang tulong. Ito ay ganap na hindi mapag-aalinlanganan na ang problemang ito ay maaaring malutas sa maraming iba pang mga paraan. Oo, lahat ng bagay na maaaring gawin sa tulong ng mga anotasyon/pagninilay ay maaaring gawin nang wala ang mga ito, ang tanging tanong ay kaginhawahan, pinakamainam at laki ng code, siyempre, idikit ang isang Anotasyon kung saan man mayroong kaunting pahiwatig na posible na gamitin hindi rin ito ang pinaka makatwirang opsyon , sa lahat ng kailangan mong malaman kung kailan titigil =). Ngunit kapag nagsusulat ng mga API, Mga Aklatan o mga programa kung saan posibleng ulitin ang parehong uri (ngunit hindi eksakto ang parehong) code, ang mga anotasyon ay walang alinlangan ang pinakamainam na solusyon.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION