JavaRush /Java блогу /Random-KY /Java @Annotations. Бул эмне жана аны кантип колдонуу кере...
SemperAnte
Деңгээл
Донецк

Java @Annotations. Бул эмне жана аны кантип колдонуу керек?

Группада жарыяланган
Бул макала Аннотациялар менен эч качан иштебеген, бирок ал эмне экенин жана эмне менен колдонуларын түшүнгүсү келген адамдарга арналган. Эгер бул жаатта тажрыйбаңыз болсо, бул макала кандайдыр бир жол менен сиздин бorмиңизди кеңейтет деп ойлобойм (жана, чындыгында, мен мындай максатты көздөбөйм). Ошондой эле, макала Java тorн жаңыдан үйрөнө баштагандар үчүн ылайыктуу эмес. Эгер сиз Map<> же HashMap<> деген эмне экенин түшүнбөсөңүз же класстын аныктамасындагы статикалык{ } жазуу эмнени билдирерин билбесеңиз , же рефлексия менен эч качан иштебесеңиз, бул макаланы окууга али эрте. annotationлар эмне экенин түшүнүүгө аракет кыл. Бул курал өзү башталгычтар үчүн жаратылган эмес, анткени ал класстардын жана an objectтердин өз ара аракеттенүүсүн толук эмес негизги түшүнүүнү талап кылат (менин пикирим) (бул посткрипттин зарылдыгын көрсөткөн комментарийлерге рахмат). Java @Annotations.  Бул эмне жана аны кантип колдонуу керек?  - 1Ошентип, баштайлы. Java тorндеги annotationлар функция/класс/пакет үчүн метаберorштерди сүрөттөгөн codeдогу энбелгилердин бир түрү. Мисалы, белгилүү @Override Annotation, бул биз ата-эне класстын ыкмасын жокко чыгара турганыбызды көрсөтөт. Ооба, бир чети ансыз деле болот, бирок ата-энелерде бул ыкма жок болсо, codeду бекер эле жазып койгонбуз да ыктымал, себеби Бул өзгөчө ыкма эч качан аталбашы мүмкүн, бирок @Override Annotation менен компилятор бизге мындай дейт: "Мен мындай ыкманы ата-энелерден тапкан жокмун ... бул жерде бир нерсе кир." Бирок, Аннотациялар "ишенимдүүлүк үчүн" маанисин гана камтышы мүмкүн эмес: алар кийинчерээк колдонула турган кээ бир маалыматтарды сактай алат.

Биринчиден, стандарттуу китепкана тарабынан берилген эң жөнөкөй annotationларды карап көрөлү.

(комментарийлерге дагы бир жолу рахмат, адегенде бул блоктун кереги жок деп ойлогон эмесмин) Биринчиден, annotationлар эмне экенин талкуулайлы. Алардын ар биринде 2 негизги талап кылынган параметр бар:
  • Сактоо түрү (сактоо);
  • Ал көрсөтүлгөн an objectтин түрү (Максат).

Сактоо түрү

"Сактоо түрү" деп биз класстын ичинде annotationбыздын "жаман" калган баскычын түшүнөбүз. Ар бир annotationда RetentionPolicy классында көрсөтүлгөн мүмкүн болгон "сактоо түрлөрүнүн" бирөө гана бар :
  • SOURCE - annotation code жазууда гана колдонулат жана компилятор тарабынан этибарга алынbyte (б.а. компиляциядан кийин сакталbyte). Көбүнчө ар кандай препроцессорлор (шарттуу) же компиляторго көрсөтмөлөр үчүн колдонулат
  • КЛАСС - annotation компиляциядан кийин сакталат, бирок JVM тарабынан этибарга алынbyte (б.а. иштөө убагында колдонулbyte). Адатта сиздин codeуңузду плагин тиркемеси катары жүктөгөн үчүнчү тараптын кызматтары үчүн колдонулат
  • RUNTIME – компиляциядан кийин сакталып, JVM тарабынан жүктөлгөн annotation (б.а. программанын өзүн аткаруу учурунда колдонулушу мүмкүн). Программанын аткарылышына түздөн-түз таасир этүүчү codeдогу белгилер катары колдонулат (мисалы бул макалада талкууланат)

Жогоруда көрсөтүлгөн an objectтин түрү

Бул сүрөттөмө дээрлик түз мааниде кабыл алынышы керек, анткени... Java тorнде annotationларды каалаган нерсенин үстүнөн көрсөтүүгө болот (Талаалар, класстар, функциялар ж.б.) жана ар бир annotation үчүн аны эмненин үстүнөн көрсөтүүгө боло тургандыгы көрсөтүлөт. Бул жерде мындан ары "бир нерсе" эрежеси жок; annotationны төмөндө келтирилгендердин бардыгынын үстүнөн көрсөтсө болот же ElementType классынын керектүү элементтерин гана тандай аласыз :
  • ANNOTATION_TYPE - башка annotation
  • КОНСТРУКТОР – класстын конструктору
  • FIELD - класс талаасы
  • LOCAL_VARIABLE - жергorктүү өзгөрмө
  • МЕТООД – класстык метод
  • PACKAGE – таңгак пакетинин сүрөттөлүшү
  • PARAMETER - метод параметри public void hello(@Annontation String param){}
  • TYPE - класстын үстүндө көрсөтүлгөн
Жалпысынан Java SE 1.8ге карата стандарттык тил китепканасы бизге 10 annotationны берет. Бул макалада биз алардын эң кеңири тараганын карап чыгабыз (алардын бардыгына кимдер кызыкдар? Javadocко кош келиңиздер ):

@Override

Сактоо: SOURCE; Максат: МЕТО. Бул annotation ал жазылган ыкма ата-эне класстан мураска калганын көрсөтүп турат. Ар бир башталгыч Java программисти ушул @Overrideди тынымсыз түрткөн IDEди колдонууда кездешүүчү биринчи annotation. Көбүнчө YouTube'дун мугалимдери: "ал тоскоол болбош үчүн аны өчүрүп сал" же "эмне үчүн ал жерде экенине таң калбай эле таштап коюуну" сунушташат. Чындыгында, annotation пайдалуу эмес: бул класста биринчи жолу кайсы методдор аныкталганын жана ата-энелерде бар экенин түшүнүүгө гана мүмкүнчүлүк бербестен (бул сиздин codeуңуздун окууга жөндөмдүүлүгүн жогорулатат), ошондой эле бул annotation ашыкча жүктөлгөн функцияны аныктоодо жаңылбаганыңыз үчүн "өзүн-өзү текшерүү" катары кызмат кылат.

@Deprecated

Сактоо: Runtime; Максат: КОНСТРУКТОР, ТАЛА, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE. Бул annotation "эскирген" жана продуктунун келечектеги versionларында алынып салынышы мүмкүн болгон ыкмаларды, класстарды же өзгөрмөлөрдү аныктайт. Бул annotationны, адатта, кандайдыр бир API documentтерин же ошол эле стандарттуу Java китепканасын окугандар кездешет. Кээде бул annotation этибарга алынbyte, анткени... ал эч кандай каталарды жаратпайт жана принципиалдуу түрдө жашоого көп кийлигишпейт. Бирок, бул annotation алып келе турган негизги билдирүү "биз бул функцияны ишке ашыруу үчүн ыңгайлуураак ыкманы ойлоп таптык, аны колдонуңуз, эскисин колдонбоңуз" - жакшы, же болбосо - "функциянын атын өзгөрттүк, бирок бул ошондой болсо, биз аны мураска калтырдык...» (бул да жалпысынан жаман эмес). Кыскача айтканда, @Deprecated дегенди көрсөңүз, анда ал өтө зарыл болбосо, аны колдонбогонго аракет кылганыңыз жакшы жана эскирген элемент аткарган тапшырма кандайча аткарылып жатканын түшүнүү үчүн documentтерди кайра окуп чыгуу керек. Мисалы, new Date().getYear() колдонуунун ордуна Calendar.getInstance().get(Calendar.YEAR) колдонуу сунушталат .

@SuppressWarnings

Сактоо: SOURCE; Максат: TYPE, FIELD, METHOD, PARAMETER, CONTRUCTOR, LOCAL_VARIABLE Бул annotation ал көрсөтүлгөн элементке тиешелүү компилятордун эскертүүлөрүнүн чыгышын өчүрөт. SOURCE annotationсы талааларда, методдордо, класстарда көрсөтүлгөнбү.

@Сактоо

Сактоо: RUNTIME; Максат: ANNOTATION_TYPE; Бул annotation жогоруда көрсөтүлгөн annotationнын "сактоо түрүн" көрсөтөт. Ооба, бул annotation өзү үчүн да колдонулат ... сыйкыр жана ушунча.

@Target

Сактоо: RUNTIME; Максат: ANNOTATION_TYPE; Бул annotation биз түзгөн annotation көрсөтүлө турган an objectтин түрүн көрсөтөт. Ооба, ал өзүң үчүн да колдонулат, көнүп ал... Менимче, ушул жерден биз Java китепканасынын стандарттык annotationлары менен таанышууну аяктай алабыз, анткени калгандары өтө сейрек колдонулат жана алардын өз пайдасы болсо да, баары эле алар менен күрөшүүгө туура келбейт жана таптакыр керексиз. Эгерде сиз стандарттуу китепканадан белгилүү бир annotation (же, балким, STLге кирбеген @NotNull жана @Nullable сыяктуу annotationлар) жөнүндө сүйлөшкүм келсе, комментарийге жазыңыз - же боорукер колдонуучулар сизге ошол жерден жооп беришет, же Мен аны көргөндө берем. Эгерде көп адамдар кандайдыр бир annotation сурашса, мен аны да макалага кошом.

RUNTIME annotationларынын практикалык колдонулушу

Чынында, менимче, бул жетиштүү теориялык сүйлөшүү: келгиле, боттун мисалын колдонуу менен практикага өтөлү. Кайсы бир социалдык тармак үчүн бот жазгыңыз келет дейли. Бардык негизги тармактар, мисалы, VK, Facebook, Discord, бот жазууга мүмкүндүк берген өздөрүнүн API'лери бар. Ушул эле тармактар ​​үчүн API, анын ичинде Java менен иштөө үчүн жазылган китепканалар бар. Ошондуктан, биз эч кандай API же китепкананын ишине киришпейбиз. Бул мисалда биз бorшибиз керек болгон нерсе, биздин бот чындыгында жайгашкан чатта жөнөтүлгөн билдирүүлөргө жооп бере алат. Башкача айтканда, бизде функциясы бар MessageListener классы бар дейли :
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Ал кабыл алынган билдирүүнү иштетүү үчүн жооптуу. MessageReceivedEvent классынан бизге керек болгон нерсе – кабыл алынган билдирүүнүн саптары (мисалы, “Салам” же “Бот, салам”). Бул карап чыгуу керек: ар кандай китепканаларда бул класстар башкача аталат. Мен китепкананы Discord үчүн колдондум. Ошентип, биз боттун "Bot" менен башталган кээ бир буйруктарга реакциясын каалайбыз (үтүр менен же үтүрсүз - өзүңүз чечиңиз: бул сабак үчүн биз ал жерде үтүр болбошу керек деп ойлойбуз). Башкача айтканда, биздин функция төмөнкүдөй нерсе менен башталат:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
Ал эми азыр бизде тигил же бул команданы ишке ашыруунун көптөгөн варианттары бар. Албетте, алгач буйрукту анын аргументтеринен ажыратыш керек, башкача айтканда, массивге бөлүү керек.
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
            //В случае если просто написать "Бот"
        }
    }
}
Коддун бул бөлүгүнөн кача албайбыз, анткени буйрукту аргументтерден бөлүү дайыма зарыл. Бирок бизде тандоо бар:
  • If(command.equalsIngnoreCase("...")) кылыңыз
  • Которуу (буйрук)
  • Башка жол менен кайра иштетүү...
  • Же Аннотациялардын жардамына кайрылыңыз.
Эми биз Аннотацияларды колдонуунун практикалык бөлүгүнө жеттик. Келгиле, биздин тапшырманын annotation codeун карап көрөлү (албетте, ар кандай болушу мүмкүн).
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();

}
Маанилүү! Ар бир параметр функция катары сүрөттөлөт (каша менен). Параметр катары примитивдерди, String , Enum гана колдонсо болот . List<String> args() деп жаза албайсыз ; - ката. Эми биз Аннотацияны сүрөттөгөнүбүздөн кийин класс түзөлү, аны 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();

    }
}
Бир кичинекей ыңгайсыздыкты белгилей кетүү керек: т.к. Биз азыр универсалдуулук үчүн күрөшүп жатабыз, бардык функциялар формалдуу параметрлердин бирдей тизмесине ээ болушу керек, ошондуктан буйрукта аргумент жок болсо дагы, функцияда String [] args параметри болушу керек . Биз азыр 3 буйрукту сүрөттөп бердик: салам, кош, жардам. Эми муну менен бир нерсе кылуу үчүн MessageListenerыбызды өзгөртөлү . Иштин ыңгайлуулугу жана ылдамдыгы үчүн биз буйруктарыбызды 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
                //В случае если просто написать "Бот"
            }
        }
    }
}
Биздин коллективдердин иштеши учун мына ушулардын бардыгы зарыл. Эми жаңы буйрукту кошуу жаңы эмес, эгерде жаңы эмес болсо, анда аргументтердин санын кайра эсептөө керек болот жана жардамды да ага жаңы саптарды кошуп, кайра жазуу керек болот. Эми, буйрук кошуу үчүн, биз жөн гана CommandListener классындагы @Command annotationсы менен жаңы функцияны кошушубуз керек жана ушуну менен - ​​буйрук кошулат, учурлар эске алынат, жардам автоматтык түрдө кошулат. Бул көйгөйдү башка көптөгөн жолдор менен чечүүгө боло тургандыгы таптакыр талашсыз. Ооба, annotationлардын/чагылуунун жардамы менен жасала турган нерселердин бардыгын аларсыз жасоого болот, бир гана маселе - ыңгайлуулук, оптималдуу жана codeдун өлчөмү, албетте, annotationны колдонууга боло турган ишарат болгон жерде жабыштырып коюу. бул эң рационалдуу вариант эмес, баардык нерседе качан токтотууну бorшиңиз керек =). Бирок API'лерди, китепканаларды же бир типтеги codeду (бирок так эмес) кайталоого мүмкүн болгон программаларды жазууда annotationлар оптималдуу чечим болуп саналат.
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION