JavaRush /בלוג Java /Random-HE /Java @הערות. מה זה ואיך להשתמש בו?
SemperAnte
רָמָה
Донецк

Java @הערות. מה זה ואיך להשתמש בו?

פורסם בקבוצה
מאמר זה מיועד לאנשים שמעולם לא עבדו עם הערות, אבל היו רוצים להבין מה זה ועם מה הוא משמש. אם יש לך ניסיון בתחום זה, אני לא חושב שהמאמר הזה ירחיב איכשהו את הידע שלך (ולמעשה, אני לא שואף למטרה כזו). כמו כן, המאמר אינו מתאים למי שרק מתחיל ללמוד את שפת ג'אווה. אם אינך מבין מהי Map<> או HashMap<> או אינך יודע מה המשמעות של הערך הסטטי{ } בתוך הגדרת מחלקה, או שמעולם לא עבדת עם השתקפות, זה מוקדם מדי בשבילך לקרוא את המאמר הזה. נסה להבין מה זה הערות. הכלי הזה עצמו לא נוצר לשימוש על ידי מתחילים, מכיוון שהוא דורש הבנה לא לגמרי בסיסית של האינטראקציה של מחלקות ואובייקטים (לדעתי) (תודה להערות שהראו את הצורך בפוסט סקריפט זה). Java @הערות.  מה זה ואיך להשתמש בו?  - 1אז בואו נתחיל. הערות ב-Java הן מעין תוויות בקוד שמתארות את המטא נתונים של פונקציה/מחלקה/חבילה. לדוגמה, הערת @Override הידועה, שמציינת שאנחנו הולכים לעקוף שיטה של ​​מחלקת האב. כן, מצד אחד אפשר בלעדיו, אבל אם אין להורים את השיטה הזו, יש אפשרות שכתבנו את הקוד לשווא, כי יתכן שלעולם לא יקרא לשיטה הספציפית הזו, אבל עם ההערה @Override המהדר יגיד לנו ש: "לא מצאתי שיטה כזו אצל ההורים... משהו מלוכלך כאן." עם זאת, הערות יכולות לשאת יותר מסתם המשמעות של "למען מהימנות": הן יכולות לאחסן כמה נתונים שישמשו מאוחר יותר.

ראשית, בואו נסתכל על ההערות הפשוטות ביותר שמספקת הספרייה הסטנדרטית.

(שוב תודה להערות, בהתחלה לא חשבתי שצריך את החסימה הזו) ראשית, בואו נדון מהן הערות. לכל אחד מהם 2 פרמטרים נדרשים עיקריים :
  • סוג אחסון (שמירה);
  • סוג האובייקט שמעליו הוא מצוין (Target).

סוג אחסון

ב"סוג אחסון" אנו מתכוונים לשלב שאליו ההערה שלנו "שורדת" בתוך הכיתה. לכל הערה יש רק אחד מ"סוגי השמירה" האפשריים שצוינו במחלקה RetentionPolicy :
  • SOURCE - ההערה משמשת רק בעת כתיבת קוד ומתעלמת מהקומפיילר (כלומר, היא לא נשמרת לאחר ההידור). משמש בדרך כלל עבור כל מעבדי קדם (בתנאי), או הוראות לקומפיילר
  • CLASS - ההערה נשמרת לאחר ההידור, אך ה-JVM מתעלמת ממנה (כלומר, לא ניתן להשתמש בה בזמן ריצה). משמש בדרך כלל עבור כל שירותי צד שלישי שטוענים את הקוד שלך כיישום פלאגין
  • RUNTIME הוא ביאור שנשמר לאחר ההידור ונטען על ידי ה-JVM (כלומר, ניתן להשתמש בו במהלך הפעלת התוכנית עצמה). משמש כסימנים בקוד המשפיעים ישירות על ביצוע התוכנית (דוגמה תידון במאמר זה)

סוג האובייקט שמעליו מצוין

יש לקחת את התיאור הזה כמעט מילולית, כי... בג'אווה ניתן לציין הערות על כל דבר (שדות, מחלקות, פונקציות וכו') ולכל הערה מצוין על מה בדיוק ניתן לציין אותה. אין כאן עוד כלל של "דבר אחד"; ניתן לציין הערה מעל כל מה שרשום למטה, או שאתה יכול לבחור רק את האלמנטים הדרושים במחלקה ElementType :
  • ANNOTATION_TYPE - הערה נוספת
  • CONSTRUCTOR - קונסטרוקטור מחלקה
  • FIELD - שדה מחלקה
  • LOCAL_VARIABLE - משתנה מקומי
  • METHOD - שיטת מחלקה
  • חבילה - תיאור חבילת החבילה
  • PARAMETER - פרמטר השיטה public void hello(@Annontation String param){}
  • TYPE - מצוין מעל הכיתה
בסך הכל, נכון ל-Java SE 1.8, ספריית השפה הסטנדרטית מספקת לנו 10 הערות. במאמר זה נסתכל על הנפוצים שבהם (מי מעוניין בכולם? ברוכים הבאים ל-Javadoc ):

@עקוף

שימור: SOURCE; יעד: שיטה. ביאור זה מראה שהשיטה שעליה היא כתובה עוברת בירושה ממחלקת האב. ההערה הראשונה שבה נתקל כל מתכנת Java מתחיל בעת שימוש ב-IDE שדוחף בהתמדה את ה-@Override הללו. לעתים קרובות, מורים מ-YouTube ממליצים: "מחק את זה כדי שזה לא יפריע", או: "תעזוב את זה בלי לתהות למה זה שם." למעשה, ההערה היא יותר משימושית: היא לא רק מאפשרת לך להבין אילו שיטות הוגדרו בכיתה זו בפעם הראשונה, ואיזה כבר יש להורים (מה שללא ספק מגדיל את קריאות הקוד שלך), אלא גם את ההערה הזו. משמש כ"בדיקה עצמית" שלא טעיתם בעת הגדרת פונקציה עמוסה מדי.

@ הוצא משימוש

שימור: זמן ריצה; יעד: CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE. הערה זו מזהה שיטות, מחלקות או משתנים שהם "מיושנים" ועשויים להיות מוסרים בגרסאות עתידיות של המוצר. את ההערה הזו נתקלים בדרך כלל אלה שקוראים את התיעוד של ממשקי API, או אותה ספריית Java סטנדרטית. לפעמים מתעלמים מהערה זו כי... הוא אינו גורם לטעויות, ובאופן עקרוני, כשלעצמו אינו מפריע הרבה לחיים. עם זאת, המסר העיקרי שהביאור זה נושא הוא "המצאנו שיטה נוחה יותר ליישום הפונקציונליות הזו, השתמשו בה, אל תשתמשו בישנה" - ובכן, או אחרת - "שינינו את שמה של הפונקציה, אבל זה אם כך, השארנו את זה למורשת..." (וזה גם בדרך כלל לא רע). בקיצור, אם אתה רואה את @Deprecated, עדיף לנסות לא להשתמש במה שהוא תלוי אלא אם כן זה הכרחי, ואולי כדאי לקרוא מחדש את התיעוד כדי להבין כיצד המשימה שבוצעה על ידי האלמנט שהוצא ממומשת כעת. לדוגמה, במקום להשתמש ב-new Date().getYear() מומלץ להשתמש ב-Calendar.getInstance().get(Calendar.YEAR) .

@SuppressWarnings

שימור: SOURCE; יעד: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE ביאור זה משבית את הפלט של אזהרות מהדר הנוגעות לאלמנט שעליו הוא צוין. האם הערת SOURCE המצוינת מעל שדות, שיטות, מחלקות.

@הַחזָקָה

שימור: RUNTIME; יעד: ANNOTATION_TYPE; הערה זו מציינת את "סוג האחסון" של ההערה שמעליו היא מצוינת. כן, ההערה הזו משמשת אפילו לעצמה... קסם וזה הכל.

@יַעַד

שימור: RUNTIME; יעד: ANNOTATION_TYPE; ביאור זה מציין את סוג האובייקט שעליו ניתן לציין את ההערה שאנו יוצרים. כן, והוא משמש גם לעצמכם, תתרגלו לזה... אני חושב שכאן נוכל להשלים את ההקדמה שלנו עם ההערות הסטנדרטיות של ספריית Java, כי השאר משמשים די נדיר ולמרות שיש להם יתרונות משלהם, לא כולם צריכים להתמודד איתם וזה מיותר לחלוטין. אם אתה רוצה שאדבר על הערה ספציפית מהספרייה הרגילה (או אולי הערות כמו @NotNull ו-@Nullable, שאינן נכללות ב-STL), כתוב בתגובות - או שמשתמשים אדיבים יענו לך שם, או אעשה זאת כשאראה את זה. אם הרבה אנשים יבקשו איזשהו ביאור, אני גם אוסיף אותו למאמר.

יישום מעשי של הערות RUNTIME

למעשה, אני חושב שזה מספיק פטפוט תיאורטי: בואו נעבור לתרגול באמצעות הדוגמה של בוט. נניח שאתה רוצה לכתוב בוט עבור רשת חברתית כלשהי. לכל הרשתות הגדולות, כמו VK, Facebook, Discord, יש ממשקי API משלהן המאפשרים לך לכתוב בוט. עבור אותן רשתות, יש כבר ספריות כתובות לעבודה עם ממשקי API, כולל ב-Java. לכן, לא נתעמק בעבודה של שום API או ספריה. כל מה שאנחנו צריכים לדעת בדוגמה זו הוא שהבוט שלנו יכול להגיב להודעות שנשלחות לצ'אט שבו הבוט שלנו נמצא בפועל. כלומר, נניח שיש לנו מחלקה של MessageListener עם פונקציה:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
היא אחראית לעיבוד ההודעה שהתקבלה. כל מה שאנחנו צריכים מהמחלקה MessageReceivedEvent הוא המחרוזת של ההודעה שהתקבלה (לדוגמה, "Hello" או "Bot, hello"). כדאי לקחת בחשבון: בספריות שונות קוראים לשיעורים האלה בצורה שונה. השתמשתי בספרייה לדיסקורד. ולכן אנחנו רוצים לגרום לבוט להגיב לכמה פקודות שמתחילות ב"בוט" (עם או בלי פסיק - תחליטו בעצמכם: לצורך השיעור הזה, נניח שלא צריך להיות שם פסיק). כלומר, הפונקציה שלנו תתחיל במשהו כמו:
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("..."))
  • עשה להחליף (פקודה)
  • תעשה דרך אחרת של עיבוד...
  • או להיעזר בביאורים.
ועכשיו סוף סוף הגענו לחלק המעשי של השימוש בהערות. בואו נסתכל על קוד ההערה עבור המשימה שלנו (הוא עשוי להיות שונה, כמובן).
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();

    }
}
ראוי לציין אי נוחות אחת קטנה: t.c. כעת אנו נלחמים על אוניברסליות, לכל הפונקציות חייבת להיות אותה רשימה של פרמטרים פורמליים, כך שגם אם לפקודה אין ארגומנטים, לפונקציה חייב להיות פרמטר 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
                //В случае если просто написать "Бот"
            }
        }
    }
}
זה כל מה שצריך כדי שהצוותים שלנו יעבדו. כעת הוספת פקודה חדשה איננה מקרה חדש, אם לא מקרה חדש, שבו יהיה צורך לחשב מחדש את מספר הארגומנטים, וכן יהיה צורך לשכתב את העזרה, להוסיף לו שורות חדשות. עכשיו, כדי להוסיף פקודה, אנחנו רק צריכים להוסיף פונקציה חדשה עם הערת @Command במחלקה CommandListener וזהו - הפקודה מתווספת, מקרים נלקחים בחשבון, עזרה מתווספת אוטומטית. אין עוררין על כך שניתן לפתור בעיה זו בדרכים רבות אחרות. כן, כל מה שאפשר לעשות בעזרת הערות/הרהורים אפשר לעשות בלעדיהם, השאלה היחידה היא נוחות, אופטימליות וגודל קוד, כמובן, הדבקת Annotation בכל מקום שיש רמז קטן שיהיה אפשר להשתמש בו זו גם לא האופציה הכי רציונלית, בכל מה שאתה צריך לדעת מתי להפסיק =). אבל כשכותבים ממשקי API, ספריות או תוכניות שבהן אפשר לחזור על אותו סוג (אך לא בדיוק אותו) קוד, הערות הן ללא ספק הפתרון האופטימלי.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION