JavaRush /Blog Java /Random-MS /Java @Anotasi. Apakah itu dan bagaimana untuk menggunakan...
SemperAnte
Tahap
Донецк

Java @Anotasi. Apakah itu dan bagaimana untuk menggunakannya?

Diterbitkan dalam kumpulan
Artikel ini ditujukan untuk orang yang tidak pernah bekerja dengan Anotasi, tetapi ingin memahami apa itu dan dengan apa ia digunakan. Jika anda mempunyai pengalaman dalam bidang ini, saya tidak fikir artikel ini entah bagaimana akan mengembangkan pengetahuan anda (dan, sebenarnya, saya tidak mengejar matlamat sedemikian). Juga, artikel itu tidak sesuai untuk mereka yang baru mula belajar bahasa Jawa. Jika anda tidak faham apa itu Map<> atau HashMap<> atau tidak tahu maksud entri statik{ } dalam definisi kelas, atau tidak pernah bekerja dengan refleksi, terlalu awal untuk anda membaca artikel ini dan cuba fahami apa itu anotasi. Alat ini sendiri tidak dicipta untuk digunakan oleh pemula, kerana ia memerlukan pemahaman yang tidak sepenuhnya asas tentang interaksi kelas dan objek (pendapat saya) (terima kasih kepada komen untuk menunjukkan keperluan untuk postskrip ini). Java @Anotasi.  Apakah itu dan bagaimana untuk menggunakannya?  - 1Jadi mari kita mulakan. Anotasi dalam Java ialah sejenis label dalam kod yang menerangkan metadata untuk fungsi/kelas/pakej. Contohnya, @Override Annotation yang terkenal, yang menunjukkan bahawa kita akan mengatasi kaedah kelas induk. Ya, dalam satu tangan, mungkin tanpa itu, tetapi jika ibu bapa tidak mempunyai kaedah ini, ada kemungkinan kami menulis kod itu dengan sia-sia, kerana Kaedah khusus ini mungkin tidak pernah dipanggil, tetapi dengan @Override Anotasi pengkompil akan memberitahu kami bahawa: "Saya tidak menemui kaedah sedemikian dalam ibu bapa... ada sesuatu yang kotor di sini." Walau bagaimanapun, Anotasi boleh membawa lebih daripada sekadar makna "untuk kebolehpercayaan": ia boleh menyimpan beberapa data yang akan digunakan kemudian.

Mula-mula, mari kita lihat anotasi paling mudah yang disediakan oleh perpustakaan standard.

(terima kasih sekali lagi kepada komen, pada mulanya saya tidak fikir blok ini diperlukan) Mula-mula, mari kita bincangkan apa itu anotasi. Setiap daripada mereka mempunyai 2 parameter utama yang diperlukan :
  • Jenis storan (Pengekalan);
  • Jenis objek yang ditunjukkan (Sasaran).

Jenis storan

Dengan "jenis storan" kami maksudkan peringkat di mana anotasi kami "bertahan" di dalam kelas. Setiap anotasi hanya mempunyai satu daripada kemungkinan "jenis pengekalan" yang dinyatakan dalam kelas RetentionPolicy :
  • SUMBER - anotasi hanya digunakan semasa menulis kod dan diabaikan oleh pengkompil (iaitu, ia tidak disimpan selepas penyusunan). Biasanya digunakan untuk mana-mana prapemproses (bersyarat), atau arahan kepada pengkompil
  • CLASS - anotasi dikekalkan selepas penyusunan, tetapi diabaikan oleh JVM (iaitu tidak boleh digunakan semasa runtime). Biasanya digunakan untuk mana-mana perkhidmatan pihak ketiga yang memuatkan kod anda sebagai aplikasi pemalam
  • RUNTIME ialah anotasi yang disimpan selepas penyusunan dan dimuatkan oleh JVM (iaitu boleh digunakan semasa pelaksanaan program itu sendiri). Digunakan sebagai tanda dalam kod yang secara langsung mempengaruhi pelaksanaan program (contoh akan dibincangkan dalam artikel ini)

Jenis objek di atas yang ditunjukkan

Penerangan ini harus diambil hampir secara literal, kerana... dalam Java, anotasi boleh ditentukan atas apa-apa sahaja (Field, kelas, fungsi, dll.) dan untuk setiap anotasi ia ditunjukkan apa sebenarnya yang boleh ditentukan. Tiada lagi peraturan "satu perkara" di sini; anotasi boleh ditentukan di atas semua yang disenaraikan di bawah, atau anda boleh memilih hanya elemen yang diperlukan bagi kelas ElementType :
  • ANNOTATION_TYPE - anotasi lain
  • PEMBINA - pembina kelas
  • BIDANG - padang kelas
  • LOCAL_VARIABLE - pembolehubah setempat
  • KAEDAH - kaedah kelas
  • PAKEJ - penerangan pakej pakej
  • PARAMETER - parameter kaedah public void hello(@Annontation String param){}
  • JENIS - ditunjukkan di atas kelas
Secara keseluruhan, mulai Java SE 1.8, pustaka bahasa standard memberikan kami 10 anotasi. Dalam artikel ini kita akan melihat yang paling biasa daripada mereka (siapa yang berminat dengan kesemuanya? Selamat datang ke Javadoc ):

@Override

Pengekalan: SUMBER; Sasaran: KAEDAH. Anotasi ini menunjukkan bahawa kaedah di mana ia ditulis diwarisi daripada kelas induk. Anotasi pertama yang ditemui oleh setiap pengaturcara Java pemula apabila menggunakan IDE yang secara berterusan menolak @Override ini. Selalunya, guru dari YouTube mengesyorkan sama ada: "padamkannya supaya ia tidak mengganggu," atau: "tinggalkannya tanpa tertanya-tanya mengapa ia ada." Sebenarnya, anotasi itu lebih berguna: ia bukan sahaja membolehkan anda memahami kaedah yang ditakrifkan dalam kelas ini buat kali pertama, dan yang ibu bapa sudah ada (yang sudah pasti meningkatkan kebolehbacaan kod anda), tetapi juga anotasi ini berfungsi sebagai "semakan sendiri" bahawa anda tidak tersilap semasa mentakrifkan fungsi terlampau beban.

@Ditamatkan

Pengekalan: Masa Jalan; Sasaran: CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE. Anotasi ini mengenal pasti kaedah, kelas atau pembolehubah yang "usang" dan mungkin dialih keluar dalam versi produk akan datang. Anotasi ini biasanya ditemui oleh mereka yang membaca dokumentasi mana-mana API, atau pustaka Java standard yang sama. Kadangkala anotasi ini diabaikan kerana... ia tidak menyebabkan sebarang kesilapan dan, pada dasarnya, dengan sendirinya tidak banyak mengganggu kehidupan. Walau bagaimanapun, mesej utama yang dibawa oleh anotasi ini ialah "kami telah menghasilkan kaedah yang lebih mudah untuk melaksanakan fungsi ini, gunakannya, jangan gunakan yang lama" - baik, atau sebaliknya - "kami menamakan semula fungsi itu, tetapi ini begitulah, kami meninggalkannya untuk warisan...” (yang juga pada umumnya tidak buruk). Ringkasnya, jika anda melihat @Deprecated, adalah lebih baik anda cuba untuk tidak menggunakan apa yang tertunda melainkan ia benar-benar perlu, dan mungkin berbaloi untuk membaca semula dokumentasi untuk memahami cara tugasan yang dilakukan oleh elemen yang ditamatkan kini dilaksanakan. Sebagai contoh, daripada menggunakan Date().getYear() baharu adalah disyorkan untuk menggunakan Calendar.getInstance().get(Calendar.YEAR) .

@SuppressWarnings

Pengekalan: SUMBER; Sasaran: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE Anotasi ini melumpuhkan output amaran pengkompil yang berkaitan dengan elemen yang ditentukan. Adakah anotasi SOURCE ditunjukkan di atas medan, kaedah, kelas.

@Pengekalan

Pengekalan: RUNTIME; Sasaran: ANNOTATION_TYPE; Anotasi ini menentukan "jenis storan" anotasi di atas yang ditentukan. Ya, anotasi ini digunakan walaupun untuk dirinya sendiri... sihir dan itu sahaja.

@Sasaran

Pengekalan: RUNTIME; Sasaran: ANNOTATION_TYPE; Anotasi ini menentukan jenis objek yang mana anotasi yang kami cipta boleh ditunjukkan. Ya, dan ia juga digunakan untuk diri sendiri, biasakan... Saya rasa di sinilah kita boleh melengkapkan pengenalan kami kepada anotasi standard perpustakaan Java, kerana selebihnya digunakan agak jarang dan, walaupun mereka mempunyai faedah mereka sendiri, tidak semua orang perlu berurusan dengan mereka dan sama sekali tidak perlu. Jika anda mahu saya bercakap tentang anotasi khusus daripada perpustakaan standard (atau, mungkin, anotasi seperti @NotNull dan @Nullable, yang tidak termasuk dalam STL), tulis dalam komen - sama ada pengguna yang baik akan menjawab anda di sana, atau Saya akan apabila saya melihatnya. Jika ramai orang meminta beberapa jenis anotasi, saya juga akan menambahkannya pada artikel.

Aplikasi praktikal anotasi RUNTIME

Sebenarnya, saya rasa itu sudah cukup sembang teori: mari teruskan berlatih menggunakan contoh bot. Katakan anda ingin menulis bot untuk beberapa rangkaian sosial. Semua rangkaian utama, seperti VK, Facebook, Discord, mempunyai API mereka sendiri yang membolehkan anda menulis bot. Untuk rangkaian yang sama ini, sudah ada perpustakaan bertulis untuk bekerja dengan API, termasuk dalam Java. Oleh itu, kami tidak akan menyelidiki kerja mana-mana API atau pustaka. Apa yang perlu kita ketahui dalam contoh ini ialah bot kita boleh membalas mesej yang dihantar ke sembang di mana bot kita sebenarnya berada. Iaitu, katakan kita mempunyai kelas MessageListener dengan fungsi:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Ia bertanggungjawab untuk memproses mesej yang diterima. Apa yang kita perlukan daripada kelas MessageReceivedEvent ialah rentetan mesej yang diterima (contohnya, "Hello" atau "Bot, hello"). Perlu dipertimbangkan: di perpustakaan yang berbeza kelas ini dipanggil secara berbeza. Saya menggunakan perpustakaan untuk Discord. Oleh itu, kami ingin membuat bot bertindak balas terhadap beberapa arahan bermula dengan "Bot" (dengan atau tanpa koma - tentukan sendiri: demi pelajaran ini, kami akan menganggap bahawa tidak sepatutnya ada koma di sana). Iaitu, fungsi kami akan bermula dengan sesuatu seperti:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
Dan kini kita mempunyai banyak pilihan untuk melaksanakan perintah ini atau itu. Tidak dinafikan, mula-mula anda perlu memisahkan arahan daripada hujahnya, iaitu, bahagikannya kepada tatasusunan.
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
            //В случае если просто написать "Бот"
        }
    }
}
Tidak ada cara kita boleh mengelakkan sekeping kod ini, kerana memisahkan arahan daripada hujah sentiasa diperlukan. Tetapi kemudian kita mempunyai pilihan:
  • Lakukan jika(command.equalsIngnoreCase("..."))
  • Tukar(arahan)
  • Lakukan cara pemprosesan lain...
  • Atau gunakan bantuan Anotasi.
Dan kini kami akhirnya mencapai bahagian praktikal menggunakan Anotasi. Mari lihat kod anotasi untuk tugas kita (tentunya mungkin berbeza).
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();

}
Penting! Setiap parameter diterangkan sebagai fungsi (dengan kurungan). Hanya primitif, String , Enum boleh digunakan sebagai parameter . Anda tidak boleh menulis List<String> args(); - kesilapan. Sekarang kita telah menerangkan Anotasi, mari buat kelas, panggil ia 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();

    }
}
Perlu diperhatikan satu kesulitan kecil: t.c. Kami kini memperjuangkan kesejagatan, semua fungsi mesti mempunyai senarai parameter formal yang sama, jadi walaupun perintah itu tidak mempunyai hujah, fungsi mesti mempunyai String [] args parameter . Kami kini telah menerangkan 3 arahan: hello, bye, help. Sekarang mari kita ubah suai MessageListener kami untuk melakukan sesuatu dengan ini. Untuk kemudahan dan kelajuan kerja, kami akan segera menyimpan arahan kami dalam 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
                //В случае если просто написать "Бот"
            }
        }
    }
}
Itu sahaja yang diperlukan untuk pasukan kami bekerja. Sekarang menambah arahan baharu bukanlah perkara baharu jika, bukan kes baharu, di mana bilangan hujah perlu dikira semula, dan bantuan juga perlu ditulis semula, menambah baris baharu padanya. Sekarang, untuk menambah arahan, kita hanya perlu menambah fungsi baharu dengan anotasi @Command dalam kelas CommandListener dan itu sahaja - arahan ditambah, kes diambil kira, bantuan ditambah secara automatik. Tidak dapat dinafikan bahawa masalah ini boleh diselesaikan dengan banyak cara lain. Ya, semua yang boleh dilakukan dengan bantuan anotasi/refleksi boleh dilakukan tanpa mereka, satu-satunya persoalan adalah kemudahan, optimum dan saiz kod, sudah tentu, melekat Anotasi di mana-mana sahaja terdapat sedikit petunjuk yang mungkin untuk digunakan ia juga bukan pilihan yang paling rasional, dalam semua perkara yang anda perlu tahu bila hendak berhenti =). Tetapi apabila menulis API, Perpustakaan atau program yang boleh mengulangi kod jenis yang sama (tetapi tidak betul-betul sama), anotasi sudah pasti merupakan penyelesaian yang optimum.
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION