JavaRush /Java blogi /Random-UZ /Java @Izohlar. Bu nima va undan qanday foydalanish kerak?...
SemperAnte
Daraja
Донецк

Java @Izohlar. Bu nima va undan qanday foydalanish kerak?

Guruhda nashr etilgan
Ushbu maqola Izohlar bilan hech qachon ishlamagan, lekin u nima ekanligini va nima bilan ishlatilishini tushunishni xohlaydigan odamlar uchun mo'ljallangan. Agar sizda bu sohada tajribangiz bo'lsa, menimcha, ushbu maqola sizning bilimingizni qandaydir tarzda kengaytiradi (va, aslida, men bunday maqsadni ko'zlamayman). Bundan tashqari, maqola Java tilini endi o'rganishni boshlaganlar uchun mos emas. Agar siz Map<> yoki HashMap<> nima ekanligini tushunmasangiz yoki sinf taʼrifi ichidagi statik{ } yozuv nimani anglatishini bilmasangiz yoki hech qachon aks ettirish bilan ishlamagan boʻlsangiz, ushbu maqolani oʻqishga hali erta. izohlar nima ekanligini tushunishga harakat qiling. Ushbu vositaning o'zi yangi boshlanuvchilar tomonidan foydalanish uchun yaratilmagan, chunki u sinflar va ob'ektlarning o'zaro ta'siri to'g'risida to'liq tushunarli bo'lmagan tushunchani talab qiladi (mening fikrimcha) (ushbu postskriptga ehtiyojni ko'rsatgan sharhlar uchun rahmat). Java @Izohlar.  Bu nima va undan qanday foydalanish kerak?  - 1Shunday qilib, keling, boshlaylik. Java tilidagi izohlar funksiya/sinf/paket uchun metama’lumotlarni tavsiflovchi koddagi yorliqlarning bir turidir. Masalan, taniqli @Override Annotatsiyasi, bu biz ota-sinf usulini bekor qilmoqchi ekanligimizni bildiradi. Ha, bir tomondan, usiz ham mumkin, lekin ota-onalarda bu usul bo'lmasa, kodni bekorga yozganimiz ehtimoli bor, chunki Ushbu maxsus usul hech qachon chaqirilmasligi mumkin, ammo @Override Annotation bilan kompilyator bizga aytadi: "Men ota-onalardan bunday usulni topmadim ... bu erda biror narsa iflos." Biroq, izohlar "ishonchlilik uchun" ma'nosidan ko'proq narsani o'z ichiga olishi mumkin: ular keyinchalik foydalaniladigan ba'zi ma'lumotlarni saqlashi mumkin.

Birinchidan, standart kutubxona tomonidan taqdim etilgan eng oddiy izohlarni ko'rib chiqaylik.

(Izohlar uchun yana bir bor rahmat, avvaliga bu blok kerak emas deb o'ylamagan edim) Birinchidan, izohlar nima ekanligini muhokama qilaylik. Ularning har biri ikkita asosiy talab qilinadigan parametrga ega:
  • Saqlash turi (saqlash);
  • U ko'rsatilgan ob'ektning turi (Target).

Saqlash turi

“Saqlash turi” deganda biz annotatsiyamiz sinf ichida “omon qoladigan” bosqichni nazarda tutamiz. Har bir izohda RetentionPolicy sinfida ko'rsatilgan mumkin bo'lgan "saqlash turlaridan" faqat bittasi mavjud :
  • SOURCE - izoh faqat kod yozishda ishlatiladi va kompilyator tomonidan e'tiborga olinmaydi (ya'ni, kompilyatsiyadan keyin saqlanmaydi). Odatda har qanday preprotsessorlar (shartli) yoki kompilyatorga ko'rsatmalar uchun ishlatiladi
  • CLASS - izoh kompilyatsiya qilingandan keyin saqlanadi, lekin JVM tomonidan e'tiborga olinmaydi (ya'ni, ish vaqtida ishlatib bo'lmaydi). Odatda sizning kodingizni plagin ilovasi sifatida yuklaydigan har qanday uchinchi tomon xizmatlari uchun ishlatiladi
  • RUNTIME - kompilyatsiyadan so'ng saqlanadigan va JVM tomonidan yuklanadigan izoh (ya'ni, dasturning o'zini bajarish paytida foydalanish mumkin). Dasturning bajarilishiga bevosita ta'sir ko'rsatadigan koddagi belgilar sifatida ishlatiladi (misol ushbu maqolada muhokama qilinadi)

Yuqorida ko'rsatilgan ob'ekt turi

Bu tavsif deyarli tom ma'noda qabul qilinishi kerak, chunki ... Java-da izohlar har qanday narsa (maydonlar, sinflar, funksiyalar va h.k.) ustidan ko'rsatilishi mumkin va har bir izoh uchun aynan nima ustida ko'rsatilishi mumkinligi ko'rsatilgan. Bu yerda endi “bitta narsa” qoidasi yo‘q; izoh quyida sanab o‘tilganlarning barchasida ko‘rsatilishi mumkin yoki siz faqat ElementType sinfining kerakli elementlarini tanlashingiz mumkin :
  • ANNOTATION_TYPE - boshqa izoh
  • KONSTRUKTOR - sinf konstruktori
  • FIELD - sinf maydoni
  • LOCAL_VARIABLE - mahalliy o'zgaruvchi
  • METOD - sinf usuli
  • PACKAGE - paket paketining tavsifi
  • PARAMETRE - metod parametri public void hello(@Annontation String param){}
  • TYPE - sinfning yuqorisida ko'rsatilgan
Jami, Java SE 1.8 dan boshlab standart til kutubxonasi bizga 10 ta izohni taqdim etadi. Ushbu maqolada biz ulardan eng keng tarqalganini ko'rib chiqamiz (ularning barchasi kimga qiziq? Javadoc-ga xush kelibsiz ):

@Override

Saqlash: SOURCE; Maqsad: METOD. Bu izoh, u yozilgan usul ota-sinfdan meros bo'lib qolganligini ko'rsatadi. Har bir yangi Java dasturchisi ushbu @Override-ni doimiy ravishda bosadigan IDE-dan foydalanganda duch keladigan birinchi izoh. Ko'pincha YouTube o'qituvchilari: "xalaqit bermasligi uchun uni o'chirib tashlang" yoki "nega u erda ekanligiga hayron bo'lmasdan qoldiring" ni tavsiya qiladi. Aslida, izoh foydaliroqdir: bu nafaqat ushbu sinfda qaysi usullar birinchi marta aniqlanganligini va qaysi ota-onalarda mavjud ekanligini tushunishga imkon beradi (bu shubhasiz kodingizning o'qilishini oshiradi), balki ushbu izohni ham tushunishga imkon beradi. haddan tashqari yuklangan funktsiyani belgilashda xato qilmaganingiz uchun "o'z-o'zini tekshirish" vazifasini bajaradi.

@Qabul qilingan

Saqlash: ish vaqti; Maqsad: KONSTRUCTOR, FIELD, LOCAL_VARIABLE, METOD, PACKAGE, PARAMETRE, TYPE. Ushbu izoh “eskirgan” va mahsulotning kelgusi versiyalarida olib tashlanishi mumkin bo'lgan usullar, sinflar yoki o'zgaruvchilarni aniqlaydi. Ushbu izoh odatda har qanday API hujjatlarini yoki bir xil standart Java kutubxonasini o'qiganlar tomonidan uchraydi. Ba'zan bu izoh e'tiborga olinmaydi, chunki... u hech qanday xatolikka olib kelmaydi va, qoida tariqasida, o'z-o'zidan hayotga ko'p aralashmaydi. Biroq, ushbu izohning asosiy xabari "biz ushbu funktsiyani amalga oshirish uchun qulayroq usulni ishlab chiqdik, undan foydalaning, eskisini ishlatmang" - yaxshi yoki boshqa - "biz funktsiyaning nomini o'zgartirdik, ammo bu shundaymi, biz uni merosga qoldirdik...” (bu ham umuman yomon emas). Muxtasar qilib aytganda, agar siz @Deprecated-ni ko'rsangiz, u o'ta zarur bo'lmasa, u osilgan narsadan foydalanmaslikka harakat qilganingiz ma'qul va eskirgan element tomonidan bajarilgan vazifa hozir qanday amalga oshirilayotganini tushunish uchun hujjatlarni qayta o'qib chiqishga arziydi. Masalan, new Date().getYear() dan foydalanish oʻrniga Calendar.getInstance().get(Calendar.YEAR) dan foydalanish tavsiya etiladi .

@SuppressWarnings

Saqlash: SOURCE; Maqsad: TYPE, FIELD, METHOD, PARAMETRE, CONSTRUCTOR, LOCAL_VARIABLE Ushbu izoh o'zi ko'rsatilgan elementga tegishli kompilyator ogohlantirishlarining chiqishini o'chiradi. SOURCE izohi yuqorida maydonlar, usullar, sinflar ko'rsatilganmi.

@Saqlash

Saqlash: RUNTIME; Maqsad: ANNOTATION_TYPE; Ushbu izoh yuqorida ko'rsatilgan izohning "saqlash turini" belgilaydi. Ha, bu izoh hatto o'zi uchun ham ishlatiladi ... sehr va bu hammasi.

@Maqsad

Saqlash: RUNTIME; Maqsad: ANNOTATION_TYPE; Ushbu izoh biz yaratgan izohni ko'rsatish mumkin bo'lgan ob'ekt turini belgilaydi. Ha, va u o'zingiz uchun ham qo'llaniladi, o'rganing... Menimcha, bu erda biz Java kutubxonasining standart annotatsiyalari bilan tanishishimizni yakunlashimiz mumkin, chunki qolganlari juda kamdan-kam qo'llaniladi va ularning o'ziga xos afzalliklari bo'lsa-da, hamma ham ular bilan shug'ullanishi shart emas va mutlaqo keraksizdir. Agar siz standart kutubxonadan ma'lum bir izoh (yoki STL ga kiritilmagan @NotNull va @Nullable kabi annotatsiyalar) haqida gapirishimni istasangiz, izohlarda yozing - yoki mehribon foydalanuvchilar sizga o'sha erda javob berishadi yoki Men uni ko'rganimda qilaman. Agar ko'p odamlar qandaydir izoh so'rasa, men uni maqolaga qo'shaman.

RUNTIME annotatsiyalarining amaliy qo'llanilishi

Aslida, menimcha, bu etarli nazariy suhbat: keling, bot misolidan foydalanib, amaliyotga o'taylik. Aytaylik, siz qandaydir ijtimoiy tarmoq uchun bot yozmoqchisiz. VK, Facebook, Discord kabi barcha yirik tarmoqlarda bot yozish imkonini beruvchi o‘z API-lari mavjud. Xuddi shu tarmoqlar uchun API bilan ishlash uchun allaqachon yozilgan kutubxonalar mavjud, shu jumladan Java. Shuning uchun biz hech qanday API yoki kutubxona ishiga kirmaymiz. Ushbu misolda bilishimiz kerak bo'lgan narsa shundaki, bizning botimiz aslida joylashgan chatga yuborilgan xabarlarga javob bera oladi. Ya'ni, bizda funksiyaga ega MessageListener sinfi bor deylik :
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Qabul qilingan xabarni qayta ishlash uchun javobgardir. MessageReceivedEvent sinfidan bizga kerak bo'lgan narsa - qabul qilingan xabarning satri (masalan, "Salom" yoki "Bot, salom"). Buni ko'rib chiqishga arziydi: turli kutubxonalarda bu sinflar boshqacha nomlanadi. Discord uchun kutubxonadan foydalandim. Shunday qilib, biz botni "Bot" dan boshlanadigan ba'zi buyruqlarga javob berishini xohlaymiz (vergul bilan yoki vergulsiz - o'zingiz qaror qiling: bu dars uchun biz bu erda vergul bo'lmasligi kerak deb taxmin qilamiz). Ya'ni, bizning funktsiyamiz quyidagicha boshlanadi:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
Va endi bizda u yoki bu buyruqni amalga oshirish uchun ko'plab variantlar mavjud. Shubhasiz, birinchi navbatda siz buyruqni argumentlaridan ajratishingiz kerak, ya'ni uni massivga bo'lishingiz kerak.
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
            //В случае если просто написать "Бот"
        }
    }
}
Ushbu kod bo'lagidan qochishning iloji yo'q, chunki buyruqni argumentlardan ajratish har doim zarur. Ammo keyin bizda tanlov bor:
  • If(command.equalsIngnoreCase("...")) bajaring.
  • O'zgartirishni amalga oshirish (buyruq)
  • Qayta ishlashning boshqa usulini bajaring ...
  • Yoki Izohlar yordamiga murojaat qiling.
Va endi biz nihoyat Izohlardan foydalanishning amaliy qismiga etib keldik. Keling, vazifamiz uchun annotatsiya kodini ko'rib chiqaylik (albatta, u farq qilishi mumkin).
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();

}
Muhim! Har bir parametr funksiya sifatida tavsiflanadi (qavslar bilan). Parametr sifatida faqat primitivlar, String , Enum dan foydalanish mumkin . List<String> args(); ni yozish mumkin emas . - xato. Endi biz Izohni tasvirlab berdik, keling, sinf yarataylik, uni CommandListener deb nomlaymiz .
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 kichik noqulaylikni ta'kidlash kerak: t.k. Biz hozir universallik uchun kurashyapmiz, barcha funksiyalar bir xil rasmiy parametrlar roʻyxatiga ega boʻlishi kerak, shuning uchun buyruqda argumentlar boʻlmasa ham, funksiya String[] args parametriga ega boʻlishi kerak . Endi biz 3 ta buyruqni tasvirlab berdik: salom, xayr, yordam. Endi bu bilan biror narsa qilish uchun MessageListenerimizni o'zgartiramiz . Ishning qulayligi va tezligi uchun biz buyruqlarimizni darhol HashMap -da saqlaymiz :
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
                //В случае если просто написать "Бот"
            }
        }
    }
}
Bu bizning jamoalar ishlashi uchun zarur bo'lgan narsadir. Endi yangi buyruq qo'shish yangi emas, agar yangi holat bo'lmasa, unda argumentlar sonini qayta hisoblash kerak bo'ladi va yordam ham qayta yozilishi va unga yangi qatorlar qo'shilishi kerak bo'ladi. Endi buyruq qo'shish uchun biz CommandListener sinfida @Command izohi bilan yangi funksiyani qo'shishimiz kerak va hammasi - buyruq qo'shiladi, holatlar hisobga olinadi, yordam avtomatik ravishda qo'shiladi. Bu muammoni boshqa ko'plab usullar bilan hal qilish mumkinligi mutlaqo shubhasizdir. Ha, izohlar/fikrlar yordamida bajarilishi mumkin bo'lgan hamma narsani ularsiz ham qilish mumkin, yagona savol - qulaylik, optimallik va kod hajmi, albatta, foydalanish mumkin bo'lgan eng kichik ishora bo'lgan joyda Annotatsiyani yopishtirish. bu ham eng oqilona variant emas , hamma narsada qachon to'xtash kerakligini bilishingiz kerak =). Ammo API, kutubxonalar yoki bir xil turdagi (lekin bir xil emas) kodni takrorlash mumkin bo'lgan dasturlarni yozishda izohlar, shubhasiz, eng maqbul echimdir.
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION