JavaRush /Java Blog /Random-ID /Java @Annotasi. Apa itu dan bagaimana cara menggunakannya...
SemperAnte
Level 4
Донецк

Java @Annotasi. Apa itu dan bagaimana cara menggunakannya?

Dipublikasikan di grup Random-ID
Artikel ini ditujukan bagi orang-orang yang belum pernah bekerja dengan Anotasi, namun ingin memahami apa itu Anotasi dan kegunaannya. Jika Anda memiliki pengalaman di bidang ini, saya rasa artikel ini tidak akan memperluas pengetahuan Anda (dan sebenarnya saya tidak mengejar tujuan seperti itu). Selain itu, artikel ini tidak cocok untuk mereka yang baru mulai belajar bahasa Java. Jika Anda tidak memahami apa itu Map<> atau HashMap<> atau tidak tahu apa arti entri static{ } di dalam definisi kelas, atau belum pernah bekerja dengan refleksi, masih terlalu dini bagi Anda untuk membaca artikel ini dan cobalah memahami apa itu anotasi. Alat ini sendiri tidak dibuat untuk digunakan oleh pemula, karena tidak memerlukan pemahaman yang sepenuhnya mendasar tentang interaksi kelas dan objek (menurut saya) (terima kasih atas komentar yang menunjukkan perlunya catatan tambahan ini). Java @Annotasi.  Apa itu dan bagaimana cara menggunakannya?  - 1Jadi mari kita mulai. Anotasi di Java adalah sejenis label dalam kode yang mendeskripsikan metadata suatu fungsi/kelas/paket. Misalnya, Anotasi @Override yang terkenal, yang menunjukkan bahwa kita akan mengganti metode kelas induk. Ya, di satu sisi bisa tanpa itu, tetapi jika orang tua tidak memiliki cara ini, ada kemungkinan kita menulis kodenya dengan sia-sia, karena Metode khusus ini mungkin tidak akan pernah dipanggil, tetapi dengan Anotasi @Override, kompiler akan memberi tahu kita bahwa: "Saya tidak menemukan metode seperti itu di induknya... ada yang kotor di sini." Namun, Anotasi dapat memiliki lebih dari sekedar arti “untuk keandalan”: Anotasi dapat menyimpan beberapa data yang akan digunakan nanti.

Pertama, mari kita lihat anotasi paling sederhana yang disediakan oleh perpustakaan standar.

(sekali lagi terima kasih atas komentarnya, awalnya saya rasa blok ini tidak diperlukan) Pertama, mari kita bahas apa itu anotasi. Masing-masing memiliki 2 parameter utama yang diperlukan :
  • Jenis penyimpanan (Retensi);
  • Jenis objek yang diindikasikan (Target).

Jenis penyimpanan

Yang kami maksud dengan “tipe penyimpanan” adalah tahap di mana anotasi kami “bertahan” di dalam kelas. Setiap anotasi hanya memiliki satu dari kemungkinan "tipe retensi" yang ditentukan dalam kelas RetentionPolicy :
  • SUMBER - anotasi hanya digunakan saat menulis kode dan diabaikan oleh kompiler (yaitu tidak disimpan setelah kompilasi). Biasanya digunakan untuk praprosesor apa pun (bersyarat), atau instruksi ke kompiler
  • CLASS - anotasi dipertahankan setelah kompilasi, tetapi diabaikan oleh JVM (yaitu tidak dapat digunakan saat runtime). Biasanya digunakan untuk layanan pihak ketiga mana pun yang memuat kode Anda sebagai aplikasi plugin
  • RUNTIME adalah anotasi yang disimpan setelah kompilasi dan dimuat oleh JVM (yaitu dapat digunakan selama eksekusi program itu sendiri). Digunakan sebagai tanda pada kode yang secara langsung mempengaruhi eksekusi program (contohnya akan dibahas di artikel ini)

Jenis objek di atas yang ditunjukkan

Deskripsi ini harus dipahami hampir secara harfiah, karena... di Java, anotasi dapat ditentukan pada apa pun (Bidang, kelas, fungsi, dll.) dan untuk setiap anotasi ditunjukkan apa sebenarnya yang dapat ditentukan. Tidak ada lagi aturan “satu hal” di sini; anotasi dapat ditentukan di atas semua yang tercantum di bawah, atau Anda dapat memilih hanya elemen yang diperlukan dari kelas ElementType :
  • ANNOTATION_TYPE - anotasi lain
  • KONSTRUKTOR - konstruktor kelas
  • BIDANG - bidang kelas
  • LOCAL_VARIABLE - variabel lokal
  • METODE - metode kelas
  • PAKET - deskripsi paket paket
  • PARAMETER - parameter metode public void hello(@Annontation String param){}
  • JENIS - ditunjukkan di atas kelas
Secara total, pada Java SE 1.8, perpustakaan bahasa standar memberi kita 10 anotasi. Pada artikel ini kita akan melihat yang paling umum (siapa yang tertarik dengan semuanya? Selamat datang di Javadoc ):

@Mengesampingkan

Retensi: SUMBER; Sasaran: METODE. Anotasi ini menunjukkan bahwa metode penulisannya diwarisi dari kelas induk. Anotasi pertama yang ditemui setiap programmer Java pemula saat menggunakan IDE yang terus-menerus mendorong @Override ini. Seringkali, guru dari YouTube merekomendasikan: “hapus agar tidak mengganggu,” atau: “biarkan tanpa bertanya-tanya mengapa itu ada di sana.” Faktanya, anotasi ini lebih dari berguna: ini tidak hanya memungkinkan Anda memahami metode mana yang didefinisikan di kelas ini untuk pertama kalinya, dan metode mana yang sudah dimiliki induknya (yang tentu saja meningkatkan keterbacaan kode Anda), tetapi juga anotasi ini berfungsi sebagai "pemeriksaan mandiri" agar Anda tidak salah saat mendefinisikan fungsi yang kelebihan beban.

@Tidak digunakan lagi

Retensi: Waktu Proses; Target: KONSTRUKTOR, FIELD, LOCAL_VARIABLE, METODE, PAKET, PARAMETER, JENIS. Anotasi ini mengidentifikasi metode, kelas, atau variabel yang "usang" dan mungkin dihapus di versi produk mendatang. Anotasi ini biasanya ditemui oleh mereka yang membaca dokumentasi API apa pun, atau pustaka Java standar yang sama. Terkadang anotasi ini diabaikan karena... itu tidak menyebabkan kesalahan apa pun dan, pada prinsipnya, tidak banyak mengganggu kehidupan. Namun, pesan utama yang dibawa oleh anotasi ini adalah “kami telah menemukan metode yang lebih nyaman untuk mengimplementasikan fungsi ini, gunakanlah, jangan gunakan yang lama” - yah, kalau tidak - “kami mengganti nama fungsinya, tapi ini begitulah, kami meninggalkannya sebagai warisan...” (yang secara umum juga lumayan). Singkatnya, jika Anda melihat @Deprecated, lebih baik mencoba untuk tidak menggunakan apa yang hang over kecuali jika benar-benar diperlukan, dan mungkin ada baiknya membaca ulang dokumentasi untuk memahami bagaimana tugas yang dilakukan oleh elemen yang tidak digunakan lagi sekarang diimplementasikan. Misalnya, daripada menggunakan new Date().getYear() disarankan untuk menggunakan Calendar.getInstance().get(Calendar.YEAR) .

@SuppressWarnings

Retensi: SUMBER; Target: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE Anotasi ini menonaktifkan output peringatan kompiler yang berkaitan dengan elemen yang ditentukan. Apakah anotasi SUMBER ditunjukkan di atas bidang, metode, kelas.

@Penyimpanan

Retensi: RUNTIME; Target: ANNOTATION_TYPE; Anotasi ini menentukan "jenis penyimpanan" dari anotasi yang di atasnya ditentukan. Ya, anotasi ini digunakan bahkan untuk dirinya sendiri... sihir dan itu saja.

@Target

Retensi: RUNTIME; Target: ANNOTATION_TYPE; Anotasi ini menentukan tipe objek yang dapat digunakan untuk menandai anotasi yang kita buat. Ya, dan itu juga digunakan untuk diri Anda sendiri, biasakanlah... Saya rasa di sinilah kita bisa menyelesaikan pengenalan kita tentang anotasi standar perpustakaan Java, karena sisanya jarang digunakan dan, meskipun memiliki manfaatnya sendiri, tidak semua orang harus menghadapinya dan sama sekali tidak diperlukan. Jika Anda ingin saya berbicara tentang anotasi tertentu dari perpustakaan standar (atau, mungkin, anotasi seperti @NotNull dan @Nullable, yang tidak termasuk dalam STL), tulis di komentar - pengguna yang baik hati akan menjawab Anda di sana, atau Aku akan melakukannya ketika aku melihatnya. Jika banyak orang yang meminta penjelasan, saya juga akan menambahkannya ke artikel.

Penerapan praktis anotasi RUNTIME

Sebenarnya, menurut saya itu sudah cukup obrolan teoretisnya: mari kita beralih ke praktik menggunakan contoh bot. Katakanlah Anda ingin menulis bot untuk beberapa jejaring sosial. Semua jaringan besar, seperti VK, Facebook, Discord, memiliki API sendiri yang memungkinkan Anda menulis bot. Untuk jaringan yang sama, sudah ada perpustakaan tertulis untuk bekerja dengan API, termasuk di Java. Oleh karena itu, kami tidak akan mempelajari pekerjaan API atau perpustakaan apa pun. Yang perlu kita ketahui dalam contoh ini adalah bot kita dapat merespons pesan yang dikirim ke chat tempat bot kita sebenarnya berada. Artinya, katakanlah kita memiliki kelas MessageListener dengan fungsi:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Ini bertanggung jawab untuk memproses pesan yang diterima. Yang kita perlukan dari kelas MessageReceivedEvent hanyalah string dari pesan yang diterima (misalnya, “Halo” atau “Bot, halo”). Perlu dipertimbangkan: di perpustakaan yang berbeda, kelas-kelas ini disebut berbeda. Saya menggunakan perpustakaan untuk Discord. Jadi kami ingin membuat bot bereaksi terhadap beberapa perintah yang dimulai dengan "Bot" (dengan atau tanpa koma - putuskan sendiri: demi pelajaran, kami berasumsi bahwa tidak boleh ada koma di sana). Artinya, fungsi kita akan dimulai dengan sesuatu seperti:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
Dan sekarang kami memiliki banyak pilihan untuk mengimplementasikan perintah ini atau itu. Tidak diragukan lagi, pertama-tama Anda perlu memisahkan perintah dari argumennya, yaitu membaginya menjadi sebuah array.
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 mungkin kita dapat menghindari potongan kode ini, karena memisahkan perintah dari argumen selalu diperlukan. Tapi kemudian kita punya pilihan:
  • Lakukan if(command.equalsIngnoreCase("..."))
  • Lakukan peralihan (perintah)
  • Lakukan cara pemrosesan yang lain...
  • Atau gunakan bantuan Anotasi.
Dan sekarang kita akhirnya mencapai bagian praktis dalam menggunakan Anotasi. Mari kita lihat kode anotasi untuk tugas kita (tentu saja mungkin berbeda).
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 digambarkan sebagai suatu fungsi (dengan tanda kurung). Hanya primitif, String , Enum yang dapat digunakan sebagai parameter . Anda tidak dapat menulis List<String> args(); - kesalahan. Sekarang kita telah menjelaskan Anotasinya, mari kita buat sebuah kelas, beri nama 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 ketidaknyamanan kecil: t.c. Kami sekarang memperjuangkan universalitas, semua fungsi harus memiliki daftar parameter formal yang sama, jadi meskipun perintah tidak memiliki argumen, fungsi tersebut harus memiliki parameter String[] args . Kami sekarang telah menjelaskan 3 perintah: halo, sampai jumpa, tolong. Sekarang mari kita modifikasi MessageListener kita untuk melakukan sesuatu dengan ini. Untuk kenyamanan dan kecepatan kerja, kami akan segera menyimpan perintah kami di 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
                //В случае если просто написать "Бот"
            }
        }
    }
}
Hanya itu yang diperlukan agar tim kami dapat bekerja. Sekarang menambahkan perintah baru bukanlah hal baru jika, bukan kasus baru, di mana jumlah argumen perlu dihitung ulang, dan bantuan juga harus ditulis ulang, menambahkan baris baru ke dalamnya. Sekarang, untuk menambahkan perintah, kita hanya perlu menambahkan fungsi baru dengan anotasi @Command di kelas CommandListener dan selesai - perintah ditambahkan, kasus diperhitungkan, bantuan ditambahkan secara otomatis. Tidak dapat disangkal bahwa masalah ini dapat diselesaikan dengan banyak cara lain. Ya, segala sesuatu yang dapat dilakukan dengan bantuan anotasi/refleksi dapat dilakukan tanpanya, satu-satunya pertanyaan adalah kenyamanan, optimalitas dan ukuran kode, tentu saja menempelkan Anotasi di mana pun ada petunjuk sekecil apa pun yang memungkinkan untuk digunakan. ini juga bukan pilihan yang paling rasional, dalam segala hal Anda perlu tahu kapan harus berhenti =). Namun saat menulis API, Perpustakaan, atau program yang memungkinkan untuk mengulang jenis kode yang sama (tetapi tidak persis sama), anotasi tidak diragukan lagi merupakan solusi optimal.
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION