JavaRush /Java Blogu /Random-AZ /Java @Annotasiyalar. Bu nədir və onu necə istifadə etmək ...
SemperAnte
Səviyyə
Донецк

Java @Annotasiyalar. Bu nədir və onu necə istifadə etmək olar?

Qrupda dərc edilmişdir
Bu məqalə Annotasiyalarla heç vaxt işləməmiş, lakin bunun nə olduğunu və nə ilə istifadə edildiyini anlamaq istəyən insanlar üçün nəzərdə tutulub. Bu sahədə təcrübəniz varsa, düşünmürəm ki, bu məqalə sizin biliklərinizi bir şəkildə genişləndirəcək (və əslində mən belə bir məqsəd güdmürəm). Həm də məqalə Java dilini yeni öyrənməyə başlayanlar üçün uyğun deyil. Əgər Map<> və ya HashMap<>- ın nə olduğunu başa düşmürsünüzsə və ya sinif tərifi daxilindəki statik { } girişinin nə demək olduğunu bilmirsinizsə və ya heç vaxt əks etdirmə ilə işləməmisinizsə, bu məqaləni oxumaq üçün hələ tezdir. annotasiyaların nə olduğunu anlamağa çalışın. Bu alətin özü yeni başlayanlar tərəfindən istifadə üçün yaradılmamışdır, çünki bu, siniflərin və obyektlərin qarşılıqlı əlaqəsi haqqında tamamilə əsas olmayan bir anlayış tələb edir (mənim fikrimcə) (bu yazıya ehtiyac olduğunu göstərmək üçün şərhlər sayəsində). Java @Annotasiyalar.  Bu nədir və onu necə istifadə etmək olar?  - 1Beləliklə, başlayaq. Java-da annotasiyalar funksiya/sinif/paket üçün metadata təsvir edən kodda bir növ etiketdir. Məsələn, tanınmış @Override Annotasiyası, bizim ana sinifin metodunu ləğv edəcəyimizi göstərir. Bəli, bir tərəfdən onsuz da mümkündür, amma valideynlərdə bu üsul yoxdursa, kodu boş yerə yazmağımız ehtimalı var, çünki Bu xüsusi metod heç vaxt çağırıla bilməz, lakin @Override Annotasiyası ilə tərtibçi bizə deyəcək: “Valideynlərdə belə bir üsul tapmadım... burada nəsə çirklidir.” Bununla belə, Annotasiyalar yalnız “etibarlılıq üçün” mənasını daşıya bilər: onlar daha sonra istifadə olunacaq bəzi məlumatları saxlaya bilərlər.

Əvvəlcə standart kitabxananın təqdim etdiyi ən sadə annotasiyalara nəzər salaq.

(şərhlərə bir daha təşəkkür edirəm, əvvəlcə bu blokun lazım olduğunu düşünmədim) Əvvəlcə annotasiyaların nə olduğunu müzakirə edək. Onların hər birinin 2 əsas tələb olunan parametri var:
  • Saxlama növü (Saxlama);
  • Üzərində göstərildiyi obyektin növü (Hədəf).

Saxlama növü

“Saxlama növü” dedikdə, annotasiyamızın sinif daxilində “sağ qaldığı” mərhələni nəzərdə tuturuq. Hər annotasiya RetentionPolicy sinifində müəyyən edilmiş mümkün "saxlama növlərindən" yalnız birinə malikdir :
  • SOURCE - annotasiya yalnız kod yazarkən istifadə olunur və tərtibçi tərəfindən nəzərə alınmır (yəni, kompilyasiyadan sonra saxlanmır). Adətən hər hansı preprosessorlar (şərti olaraq) və ya tərtibçiyə göstərişlər üçün istifadə olunur
  • CLASS - annotasiya tərtib edildikdən sonra qorunur, lakin JVM tərəfindən nəzərə alınmır (yəni iş vaxtında istifadə edilə bilməz). Adətən kodunuzu plug-in proqramı kimi yükləyən hər hansı üçüncü tərəf xidmətləri üçün istifadə olunur
  • RUNTIME tərtib edildikdən sonra saxlanılan və JVM tərəfindən yüklənən annotasiyadır (yəni proqramın özünün icrası zamanı istifadə edilə bilər). Proqramın icrasına birbaşa təsir edən kodda işarələr kimi istifadə olunur (bir nümunə bu məqalədə müzakirə olunacaq)

Yuxarıda göstərilən obyektin növü

Bu təsvir demək olar ki, hərfi mənada qəbul edilməlidir, çünki... Java-da annotasiyalar hər hansı bir şeyin (Sahələr, siniflər, funksiyalar və s.) üzərində təyin oluna bilər və hər annotasiya üçün dəqiq nəyin üzərində göstərilə biləcəyi göstərilir. Burada artıq “bir şey” qaydası yoxdur; annotasiya aşağıda sadalanan hər şeyin üstündə göstərilə bilər və ya siz yalnız ElementType sinifinin lazımi elementlərini seçə bilərsiniz :
  • ANNOTATION_TYPE - başqa annotasiya
  • CONTRUCTOR - sinif konstruktoru
  • FIELD - sinif sahəsi
  • LOCAL_VARIABLE - yerli dəyişən
  • METOD - sinif metodu
  • PAKET - bağlama paketinin təsviri
  • PARAMETRE - metod parametri public void hello(@Annontation String param){}
  • TYPE - sinifin yuxarısında göstərilmişdir
Ümumilikdə, Java SE 1.8-dən etibarən standart dil kitabxanası bizə 10 annotasiya təqdim edir. Bu yazıda onlardan ən çox yayılmışlarına baxacağıq (hamısı ilə kimlər maraqlanır? Javadoc-a xoş gəlmisiniz ):

@Qayda

Saxlama: SOURCE; Hədəf: METOD. Bu annotasiya onun yazıldığı metodun ana sinifdən miras qaldığını göstərir. Hər bir təcrübəsiz Java proqramçısının bu @Override-ni davamlı olaraq itələyən bir IDE istifadə edərkən rastlaşdığı ilk annotasiya. Çox vaxt YouTube-dan müəllimlər məsləhət görürlər: “mənə olmamaq üçün onu silin” və ya “niyə orada olduğuna təəccüblənmədən onu tərk edin”. Əslində, annotasiya daha çox faydalıdır: o, nəinki bu sinifdə ilk dəfə hansı metodların müəyyən edildiyini və valideynlərin artıq hansı metodlara sahib olduğunu (şübhəsiz ki, kodunuzun oxunuşunu artırır), həm də bu annotasiyanı anlamağa imkan verir. həddən artıq yüklənmiş funksiyanı təyin edərkən səhv etmədiyiniz “özünü yoxlama” kimi xidmət edir.

@köhnəlmişdir

Saxlama: İcra müddəti; Hədəf: KONSTRUCTOR, SAHƏ, YERLİ_DƏYİŞƏN, METOD, PAKET, PARAMETRE, TİP. Bu annotasiya "köhnəlmiş" metodları, sinifləri və ya dəyişənləri müəyyən edir və məhsulun gələcək versiyalarında silinə bilər. Bu annotasiya ilə adətən hər hansı API-lərin sənədlərini və ya eyni standart Java kitabxanasını oxuyanlar rastlaşır. Bəzən bu annotasiya nəzərə alınmır, çünki... heç bir səhvə səbəb olmur və prinsipcə özlüyündə həyata çox da mane olmur. Bununla belə, bu annotasiyanın daşıdığı əsas mesaj “biz bu funksiyanı həyata keçirmək üçün daha rahat üsul tapdıq, ondan istifadə edin, köhnəsini istifadə etməyin” – yaxşı, yoxsa – “biz funksiyanın adını dəyişdik, lakin bu belədir, biz onu miras qoyduq...” (bu da ümumiyyətlə pis deyil). Qısacası, əgər @Deprecated görürsünüzsə, tamamilə zərurət olmadıqca onun asdığı ​​şeylərdən istifadə etməməyə çalışmaq daha yaxşıdır və köhnəlmiş elementin yerinə yetirdiyi tapşırığın indi necə həyata keçirildiyini başa düşmək üçün sənədləri yenidən oxumağa dəyər ola bilər. Məsələn, new Date().getYear() funksiyasından istifadə etmək əvəzinə Calendar.getInstance().get(Calendar.YEAR) funksiyasından istifadə etmək tövsiyə olunur .

@SuppressWarnings

Saxlama: SOURCE; Hədəf: TYPE, FIELD, METHOD, PARAMETRE, CONSTRUCTOR, LOCAL_VARIABLE Bu annotasiya qeyd olunduğu elementə aid olan kompilyator xəbərdarlıqlarının çıxışını qeyri-aktiv edir. Yuxarıda göstərilən SOURCE annotasiyası sahələr, üsullar, siniflərdir.

@Saxlama

Saxlama: RUNTIME; Hədəf: ANNOTATION_TYPE; Bu annotasiya yuxarıda göstərildiyi annotasiyanın "saxlama növü"nü müəyyən edir. Bəli, bu annotasiya hətta özü üçün də istifadə olunur... sehr və hamısı budur.

@Hədəf

Saxlama: RUNTIME; Hədəf: ANNOTATION_TYPE; Bu annotasiya yaratdığımız annotasiyanın göstərilə biləcəyi obyektin növünü müəyyən edir. Bəli, həm də özünüz üçün istifadə olunur, öyrəşin... Məncə, Java kitabxanasının standart annotasiyaları ilə tanışlığımızı burada tamamlaya bilərik, çünki qalanları olduqca nadir hallarda istifadə olunur və öz üstünlükləri olsa da, hər kəs onlarla məşğul olmur və tamamilə lazımsızdır. Standart kitabxanadan konkret annotasiya (yaxud STL-yə daxil olmayan @NotNull və @Nullable kimi annotasiyalar) haqqında danışmağımı istəyirsinizsə, şərhlərdə yazın - ya mehriban istifadəçilər sizə orada cavab verəcəklər, ya da görəndə edəcəm. Çox adam hansısa annotasiya istəsə, mən də məqaləyə əlavə edəcəm.

RUNTIME annotasiyalarının praktik tətbiqi

Əslində, məncə, bu kifayət qədər nəzəri söhbətdir: gəlin bot nümunəsindən istifadə edərək təcrübəyə keçək. Tutaq ki, siz hansısa sosial şəbəkə üçün bot yazmaq istəyirsiniz. VK, Facebook, Discord kimi bütün əsas şəbəkələrin bot yazmağa imkan verən öz API-ləri var. Bu eyni şəbəkələr üçün Java da daxil olmaqla API-lərlə işləmək üçün artıq yazılı kitabxanalar mövcuddur. Buna görə də biz heç bir API və ya kitabxananın işini araşdırmayacağıq. Bu nümunədə bilməmiz lazım olan tək şey odur ki, botumuz əslində yerləşdiyi söhbətə göndərilən mesajlara cavab verə bilər. Yəni tutaq ki, funksiyası olan MessageListener sinfimiz var:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Alınan mesajın işlənməsi üçün məsuliyyət daşıyır. MessageReceivedEvent sinfindən bizə lazım olan tək şey alınan mesajın sətridir (məsələn, “Salam” və ya “Bot, salam”). Nəzərə almağa dəyər: müxtəlif kitabxanalarda bu siniflər fərqli adlanır. Discord üçün kitabxanadan istifadə etdim. Beləliklə, biz botun "Bot" ilə başlayan bəzi əmrlərə reaksiya verməsini istəyirik (vergüllə və ya vergülsüz - özünüz qərar verin: bu dərs üçün orada vergül olmamalı olduğunu güman edəcəyik). Yəni bizim funksiyamız belə bir şeylə başlayacaq:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
İndi bu və ya digər əmri yerinə yetirmək üçün bir çox variantımız var. Şübhəsiz ki, əvvəlcə əmri onun arqumentlərindən ayırmaq, yəni onu massilə bölmək lazımdır.
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
            //В случае если просто написать "Бот"
        }
    }
}
Bu kod parçasından qaçmağın heç bir yolu yoxdur, çünki əmri arqumentlərdən ayırmaq həmişə zəruridir. Ancaq sonra seçimimiz var:
  • if(command.equalsIngnoreCase("...")) edin
  • Keçidin (əmr)
  • Başqa cür emal edin...
  • Və ya Annotasiyaların köməyinə müraciət edin.
İndi biz nəhayət Annotasiyalardan istifadənin praktik hissəsinə çatdıq. Tapşırığımızın annotasiya koduna baxaq (əlbəttə ki, fərqli ola bilər).
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();

}
Vacibdir! Hər bir parametr funksiya kimi təsvir olunur (mötərizələrlə). Parametrlər kimi yalnız primitivlər, String , Enum istifadə edilə bilər . Siz List<String> args() yaza bilməzsiniz ; - səhv. İndi Annotasiyanı təsvir etdikdən sonra sinif yaradaq, onu CommandListener adlandıraq .
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();

    }
}
Bir kiçik narahatlığı qeyd etmək lazımdır: t.c. Biz indi universallıq uğrunda mübarizə aparırıq, bütün funksiyalar eyni formal parametrlər siyahısına malik olmalıdır, ona görə də komandanın arqumentləri olmasa belə, funksiyanın String [] args parametri olmalıdır . İndi biz 3 əmri təsvir etdik: salam, sağol, kömək. İndi bununla nəsə etmək üçün MessageListenerimizi dəyişdirək . İşin rahatlığı və sürəti üçün əmrlərimizi dərhal HashMap -da saxlayacağıq :
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
                //В случае если просто написать "Бот"
            }
        }
    }
}
Komandalarımızın işləməsi üçün lazım olan hər şey budur. İndi yeni bir əmr əlavə etmək, arqumentlərin sayının yenidən hesablanması lazım olduğu yeni bir vəziyyət deyilsə, yeni deyil və kömək də ona yeni sətirlər əlavə edilməklə yenidən yazılmalıdır. İndi komanda əlavə etmək üçün sadəcə CommandListener sinfində @Command annotasiyası ilə yeni funksiya əlavə etməliyik və bu qədər - əmr əlavə olunur, hallar nəzərə alınır, yardım avtomatik olaraq əlavə olunur. Bu problemin bir çox başqa yollarla həll oluna biləcəyi tamamilə mübahisəsizdir. Bəli, annotasiyaların/fikirlərin köməyi ilə edilə bilən hər şey onlarsız edilə bilər, yeganə sual rahatlıq, optimallıq və kod ölçüsüdür, əlbəttə ki, istifadənin mümkün olacağına dair ən kiçik bir işarə olan yerdə Annotasiya yapışdırmaq. həm də ən rasional seçim deyil , nə vaxt dayanacağını bilmək lazım olan hər şeydə =). Ancaq eyni tipli (lakin eyni deyil) kodu təkrarlamaq mümkün olan API, Kitabxanalar və ya proqramlar yazarkən, annotasiyalar, şübhəsiz ki, optimal həll yoludur.
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION