JavaRush /Java блогы /Random-KK /Java @Annotations. Бұл не және оны қалай пайдалануға бола...
SemperAnte
Деңгей
Донецк

Java @Annotations. Бұл не және оны қалай пайдалануға болады?

Топта жарияланған
Бұл мақала Аннотациялармен ешқашан жұмыс істемеген, бірақ оның не екенін және немен қолданылатынын түсінгісі келетін адамдарға арналған. Егер сізде осы салада тәжірибеңіз болса, бұл мақала сіздің біліміңізді кеңейтеді деп ойламаймын (және шын мәнінде мен мұндай мақсатты көздемеймін). Сондай-ақ, мақала Java тілін жаңадан үйрене бастағандар үшін жарамайды. Егер сіз Map<> немесе HashMap<> дегеннің не екенін түсінбесеңіз немесе сынып анықтамасындағы статикалық{ } жазба нені білдіретінін білмесеңіз немесе ешқашан рефлексиямен жұмыс істемеген болсаңыз, бұл мақаланы оқып шығуға әлі ерте. annotationлардың не екенін түсінуге тырысыңыз. Бұл құралдың өзі жаңадан бастағандар үшін жасалмаған, өйткені ол сыныптар мен an objectілердің өзара әрекеттесуін мүлдем қарапайым емес түсінуді талап етеді (менің пікірім) (осы постскрипттің қажеттілігін көрсету үшін түсініктемелердің арқасында). Java @Annotations.  Бұл не және оны қалай пайдалануға болады?  - 1Ендеше, бастайық. Java тіліндегі annotationлар функция/сынып/бума үшін метадеректерді сипаттайтын codeтағы белгілердің бір түрі болып табылады. Мысалы, ата-аналық сыныптың әдісін қайта анықтайтынымызды көрсететін белгілі @Override annotationсы. Иә, бір жағынан онсыз да мүмкін, бірақ ата-анада бұл әдіс жоқ болса, codeты босқа жазғанымыз мүмкін, себебі Бұл нақты әдіс ешқашан шақырылмауы мүмкін, бірақ @Override Annotation көмегімен компилятор бізге: «Мен мұндай әдісті ата-аналардан таппадым... мұнда бірдеңе лас» деп айтады. Дегенмен, Аннотациялар тек «сенімділік үшін» мағынасын ғана емес: олар кейінірек пайдаланылатын кейбір деректерді сақтай алады.

Алдымен стандартты кітапхана ұсынған қарапайым annotationларды қарастырайық.

(пікірлерге тағы да рахмет, басында бұл блок қажет емес деп ойладым) Алдымен, annotationлардың не екенін талқылайық. Олардың әрқайсысында 2 негізгі қажетті параметр бар:
  • Сақтау түрі (Сақтау);
  • Ол көрсетілген нысанның түрі (Нысана).

Сақтау түрі

«Сақтау түрі» деп біз annotationмыздың сынып ішінде «сақталатын» кезеңін түсінеміз. Әрбір annotationда RetentionPolicy сыныбында көрсетілген ықтимал «сақтау түрлерінің» біреуі ғана болады :
  • SOURCE - annotation codeты жазғанда ғана пайдаланылады және оны компилятор елемейді (яғни компиляциядан кейін ол сақталмайды). Әдетте кез келген препроцессорлар (шартты түрде) немесе компиляторға нұсқаулар үшін қолданылады
  • CLASS - annotation компиляциядан кейін сақталады, бірақ JVM елемейді (яғни, орындау уақытында пайдалану мүмкін емес). Әдетте codeыңызды қосылатын модуль қолданбасы ретінде жүктейтін кез келген үшінші тарап қызметтері үшін пайдаланылады
  • RUNTIME — компиляциядан кейін сақталатын және JVM арқылы жүктелетін annotation (яғни, бағдарламаның өзін орындау кезінде пайдалануға болады). Бағдарламаның орындалуына тікелей әсер ететін codeтағы белгілер ретінде пайдаланылады (мысал осы мақалада талқыланады)

Жоғарыда көрсетілген нысан түрі

Бұл сипаттаманы сөзбе-сөз қабылдау керек, өйткені... Java тілінде annotationларды кез келген нәрсенің үстінен көрсетуге болады (Өрістер, сыныптар, функциялар және т.б.) және әрбір annotation үшін нақты ненің үстінен көрсетуге болатыны көрсетіледі. Бұл жерде енді «бір нәрсе» ережесі жоқ; annotationны төменде көрсетілгендердің барлығында көрсетуге болады немесе ElementType класының қажетті элементтерін ғана таңдауға болады :
  • ANNOTATION_TYPE - басқа annotation
  • CONTRUCTOR – класс конструкторы
  • FIELD – класс өрісі
  • LOCAL_VARIABLE – жергілікті айнымалы
  • ӘДІС – сынып әдісі
  • ПАКЕТ – бума пакетінің сипаттамасы
  • PARAMETER - әдіс параметрі public void hello(@Annontation String параметрі){}
  • TYPE - сыныптың үстінде көрсетілген
Жалпы Java SE 1.8 нұсқасы бойынша стандартты тіл кітапханасы бізге 10 annotation береді. Бұл мақалада біз олардың ең көп тарағандарын қарастырамыз (олардың барлығына кім қызығушылық танытады? Javadoc-қа қош келдіңіз ):

@Override

Сақтау: SOURCE; Мақсаты: ӘДІС. Бұл annotation ол жазылған әдіс ата-аналық сыныптан мұраланғанын көрсетеді. Әрбір жаңадан бастаған Java бағдарламашысы осы @Override-ді тұрақты түрде итеретін IDE пайдалану кезінде кездесетін алғашқы annotation. Көбінесе YouTube мұғалімдері: «кедергі болмас үшін оны өшіріп тастаңыз» немесе «неге бар екенін білмей қалдырыңыз» деп кеңес береді. Шын мәнінде, annotation пайдалы емес: ол осы сыныпта қандай әдістер алғаш рет анықталғанын және ата-аналарда бұрыннан бар екенін түсінуге ғана емес (бұл сіздің codeыңыздың оқылуын арттыратыны сөзсіз), сонымен қатар бұл annotationны да түсінуге мүмкіндік береді. шамадан тыс жүктелген функцияны анықтау кезінде қателеспеген «өзін-өзі тексеру» ретінде қызмет етеді.

@Ескертілген

Сақтау: Орындалу уақыты; Мақсат: КОНСТРУКТОР, ӨРІС, ЖЕРГІЛІКТІ_АЙНЫСЫ, ӘДІС, ПАКЕТ, ПАРАМЕТР, ТҮР. Бұл annotation «ескірген» және өнімнің болашақ нұсқаларында жойылуы мүмкін әдістерді, сыныптарды немесе айнымалыларды анықтайды. Бұл annotationны әдетте кез келген API құжаттамасын немесе бірдей стандартты Java кітапханасын оқитындар кездестіреді. Кейде бұл annotation еленбейді, себебі... ол ешқандай қателік тудырмайды және негізінен өмірге көп кедергі жасамайды. Дегенмен, бұл annotation беретін негізгі хабар: «біз бұл функцияны жүзеге асырудың ыңғайлы әдісін ойлап таптық, оны қолданыңыз, ескісін қолданбаңыз» - жақсы немесе басқаша - «функцияның атын өзгерттік, бірақ бұл солай, біз оны мұраға қалдырдық...» (бұл да жалпы жаман емес). Қысқаша айтқанда, егер @Deprecated көрсеңіз, өте қажет болмаса, ол тоқтап тұрған нәрсені пайдаланбауға тырысқаныңыз жөн және ескірген элемент орындаған тапсырманың енді қалай орындалатынын түсіну үшін құжаттаманы қайта оқып шыққан жөн. Мысалы, 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ны көрсетуге болатын нысан түрін көрсетеді. Иә, және ол өзіңіз үшін де қолданылады, үйреніңіз... Менің ойымша, осы жерде біз Java кітапханасының стандартты annotationларымен таныстыруды аяқтай аламыз, өйткені қалғандары өте сирек пайдаланылады және олардың өзіндік артықшылықтары болса да, олармен әркім айналыса бермейді және мүлдем қажет емес. Егер стандартты кітапханадан белгілі бір annotation (немесе, мүмкін, STL-ге кірмейтін @NotNull және @Nullable сияқты annotationлар) туралы сөйлескім келсе, түсініктемелерде жазыңыз - мейірімді пайдаланушылар сізге сол жерде жауап береді немесе Мен оны көргенде аламын. Егер көптеген адамдар қандай да бір annotationны сұраса, мен оны мақалаға қосамын.

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

Шындығында, менің ойымша, бұл жеткілікті теориялық әңгіме: бот мысалын қолдана отырып, тәжірибеге көшейік. Сіз қандай да бір әлеуметтік желіге бот жазғыңыз келеді делік. VK, Facebook, Discord сияқты барлық негізгі желілерде бот жазуға мүмкіндік беретін өздерінің API интерфейстері бар. Дәл осы желілер үшін API интерфейстерімен, соның ішінде Java-мен жұмыс істеуге арналған жазылған кітапханалар бар. Сондықтан біз кез келген API немесе кітапхананың жұмысына араласпаймыз. Бұл мысалда білуіміз керек нәрсе - біздің бот шын мәнінде орналасқан чатқа жіберілген хабарларға жауап бере алады. Яғни, бізде функциясы бар MessageListener сыныбы бар делік:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Ол қабылданған хабарламаны өңдеуге жауапты. MessageReceivedEvent сыныбынан бізге қажет нәрсе – алынған хабардың жолы (мысалы, «Hello» немесе «Bot, hello»). Осыны ескерген жөн: әртүрлі кітапханаларда бұл сыныптар әртүрлі аталады. Мен кітапхананы 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
            //В случае если просто написать "Бот"
        }
    }
}
Бұл code бөлігін болдырмауға ешқандай мүмкіндік жоқ, өйткені пәрменді аргументтерден бөлу әрқашан қажет. Бірақ бізде таңдау бар:
  • 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 өлшемі, әрине, Аннотацияны қолдануға болатыны туралы ең аз кеңес бар жерде жабыстырыңыз. бұл сонымен қатар ең ұтымды нұсқа емес, барлығында қашан тоқтату керектігін білу керек =). Бірақ API, кітапханалар немесе бір типті (бірақ дәл бірдей емес) codeты қайталауға болатын бағдарламаларды жазғанда, annotationлар сөзсіз оңтайлы шешім болып табылады.
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION