JavaRush /Blog Java /Random-VI /@Chú thích Java. Nó là gì và làm thế nào để sử dụng nó?
SemperAnte
Mức độ
Донецк

@Chú thích Java. Nó là gì và làm thế nào để sử dụng nó?

Xuất bản trong nhóm
Bài viết này dành cho những người chưa bao giờ làm việc với Chú thích nhưng muốn hiểu nó là gì và nó được sử dụng với mục đích gì. Nếu bạn có kinh nghiệm trong lĩnh vực này, tôi không nghĩ rằng bài viết này sẽ phần nào mở rộng kiến ​​​​thức của bạn (và trên thực tế, tôi không theo đuổi mục tiêu như vậy). Ngoài ra, bài viết không phù hợp với những người mới bắt đầu học ngôn ngữ Java. Nếu bạn không hiểu Map<> hoặc HashMap<> là gì hoặc không biết mục static{ } bên trong định nghĩa lớp nghĩa là gì hoặc chưa bao giờ làm việc với sự phản chiếu, thì còn quá sớm để bạn đọc bài viết này và cố gắng hiểu chú thích là gì. Bản thân công cụ này không được tạo ra để người mới bắt đầu sử dụng vì nó đòi hỏi sự hiểu biết không hoàn toàn cơ bản về sự tương tác giữa các lớp và đối tượng (ý kiến ​​của tôi) (cảm ơn các nhận xét đã cho thấy sự cần thiết của phần tái bút này). @Chú thích Java.  Nó là gì và làm thế nào để sử dụng nó?  - 1Vậy hãy bắt đầu. Chú thích trong Java là một loại nhãn trong mã mô tả siêu dữ liệu cho một hàm/lớp/gói. Ví dụ: Chú thích @Override nổi tiếng, cho biết rằng chúng ta sẽ ghi đè một phương thức của lớp cha. Có, một mặt, có thể không có nó, nhưng nếu cha mẹ không có phương pháp này, có khả năng chúng ta viết mã một cách vô ích, bởi vì Phương thức cụ thể này có thể không bao giờ được gọi, nhưng với Chú thích @Override, trình biên dịch sẽ cho chúng ta biết rằng: "Tôi không tìm thấy một phương thức như vậy ở cha mẹ... có gì đó bẩn thỉu ở đây." Tuy nhiên, Chú thích có thể mang nhiều ý nghĩa hơn là chỉ “đảm bảo độ tin cậy”: chúng có thể lưu trữ một số dữ liệu sẽ được sử dụng sau này.

Trước tiên, hãy xem các chú thích đơn giản nhất được thư viện chuẩn cung cấp.

(một lần nữa xin cảm ơn các nhận xét, lúc đầu tôi không nghĩ rằng khối này là cần thiết) Trước tiên, hãy thảo luận xem chú thích là gì. Mỗi người trong số họ có 2 tham số bắt buộc chính :
  • Loại lưu trữ (Retention);
  • Loại đối tượng được chỉ định (Mục tiêu).

Loại lưu trữ

Theo “loại lưu trữ”, chúng tôi muốn nói đến giai đoạn mà chú thích của chúng tôi “tồn tại” bên trong lớp. Mỗi chú thích chỉ có một trong các "loại lưu giữ" có thể được chỉ định trong lớp RetentionPolicy :
  • NGUỒN - chú thích chỉ được sử dụng khi viết mã và bị trình biên dịch bỏ qua (tức là nó không được lưu sau khi biên dịch). Thường được sử dụng cho mọi bộ tiền xử lý (có điều kiện) hoặc hướng dẫn cho trình biên dịch
  • LỚP - chú thích được giữ nguyên sau khi biên dịch, nhưng bị JVM bỏ qua (tức là không thể sử dụng trong thời gian chạy). Thường được sử dụng cho mọi dịch vụ của bên thứ ba tải mã của bạn dưới dạng ứng dụng trình cắm
  • RUNTIME là một chú thích được lưu sau khi biên dịch và tải bởi JVM (tức là có thể được sử dụng trong quá trình thực thi chương trình). Được sử dụng làm dấu trong mã ảnh hưởng trực tiếp đến việc thực thi chương trình (một ví dụ sẽ được thảo luận trong bài viết này)

Loại đối tượng trên được chỉ định

Mô tả này nên được hiểu gần như theo nghĩa đen, bởi vì... trong Java, các chú thích có thể được chỉ định trên bất kỳ thứ gì (Trường, lớp, hàm, v.v.) và đối với mỗi chú thích, nó được chỉ định chính xác những gì nó có thể được chỉ định. Ở đây không còn quy tắc “một thứ” nữa; một chú thích có thể được chỉ định phía trên mọi thứ được liệt kê bên dưới hoặc bạn chỉ có thể chọn các thành phần cần thiết của lớp ElementType :
  • ANNOTATION_TYPE - chú thích khác
  • CONSTRUCTOR - hàm tạo của lớp
  • TRƯỜNG - trường lớp
  • LOCAL_VARIABLE - biến cục bộ
  • PHƯƠNG PHÁP - phương thức lớp
  • GÓI - mô tả gói gói
  • THAM SỐ - tham số phương thức public void hello(@Annontation String param){}
  • LOẠI - được chỉ định ở trên lớp
Tổng cộng, kể từ Java SE 1.8, thư viện ngôn ngữ tiêu chuẩn cung cấp cho chúng ta 10 chú thích. Trong bài viết này, chúng ta sẽ xem xét những cái phổ biến nhất trong số đó (ai quan tâm đến tất cả chúng? Chào mừng bạn đến với Javadoc ):

@Ghi đè

Lưu giữ: NGUỒN; Mục tiêu: PHƯƠNG PHÁP. Chú thích này cho thấy rằng phương thức mà nó được viết được kế thừa từ lớp cha. Chú thích đầu tiên mà mọi lập trình viên Java mới làm quen đều gặp phải khi sử dụng IDE liên tục đẩy các @Override này. Thông thường, các giáo viên trên YouTube khuyên bạn: “xóa nó đi để không gây trở ngại” hoặc: “cứ để nó mà không thắc mắc tại sao nó lại ở đó”. Trên thực tế, chú thích còn hữu ích hơn nhiều: nó không chỉ cho phép bạn hiểu phương thức nào được định nghĩa trong lớp này lần đầu tiên và phương thức nào cha mẹ đã có (điều này chắc chắn làm tăng khả năng đọc mã của bạn), mà còn cả chú thích này đóng vai trò như một "sự tự kiểm tra" mà bạn không nhầm lẫn khi xác định một hàm quá tải.

@Không dùng nữa

Duy trì: Thời gian chạy; Mục tiêu: CÔNG CỤ XÂY DỰNG, TRƯỜNG, LOCAL_VARIABLE, PHƯƠNG PHÁP, GÓI, THAM SỐ, LOẠI. Chú thích này xác định các phương thức, lớp hoặc biến "lỗi thời" và có thể bị xóa trong các phiên bản tương lai của sản phẩm. Những người đọc tài liệu của bất kỳ API nào hoặc cùng một thư viện Java tiêu chuẩn thường gặp phải chú thích này. Đôi khi chú thích này bị bỏ qua vì... nó không gây ra bất kỳ sai sót nào và về nguyên tắc, bản thân nó không ảnh hưởng nhiều đến cuộc sống. Tuy nhiên, thông điệp chính mà chú thích này mang đến là “chúng tôi đã nghĩ ra một phương pháp thuận tiện hơn để triển khai chức năng này, hãy sử dụng nó, không sử dụng cái cũ” - à, nếu không thì - “chúng tôi đã đổi tên hàm, nhưng cái này là vậy, chúng tôi đã để lại nó cho di sản…” (điều này nhìn chung cũng không tệ). Nói tóm lại, nếu bạn thấy @Deprecated, tốt hơn hết bạn nên cố gắng không sử dụng những gì nó treo trừ khi nó thực sự cần thiết và có thể đáng để đọc lại tài liệu để hiểu cách thực hiện tác vụ của phần tử không được dùng nữa. Ví dụ: thay vì sử dụng new Date().getYear() bạn nên sử dụng Calendar.getInstance().get(Calendar.YEAR) .

@SuppressCảnh báo

Lưu giữ: NGUỒN; Mục tiêu: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE Chú thích này vô hiệu hóa đầu ra của các cảnh báo trình biên dịch liên quan đến phần tử mà nó được chỉ định. Chú thích SOURCE có được chỉ định ở trên các trường, phương thức, lớp hay không.

@Giữ lại

Duy trì: THỜI GIAN CHẠY; Mục tiêu: ANNOTATION_TYPE; Chú thích này chỉ định "loại lưu trữ" của chú thích ở trên mà nó được chỉ định. Đúng, chú thích này thậm chí còn được sử dụng cho chính nó... phép thuật và chỉ vậy thôi.

@Mục tiêu

Duy trì: THỜI GIAN CHẠY; Mục tiêu: ANNOTATION_TYPE; Chú thích này chỉ định loại đối tượng mà chú thích mà chúng ta tạo có thể được chỉ định. Có, và nó cũng được sử dụng cho chính bạn, hãy làm quen với nó... Tôi nghĩ đây là nơi chúng ta có thể hoàn thành phần giới thiệu về các chú thích tiêu chuẩn của thư viện Java, bởi vì phần còn lại khá hiếm khi được sử dụng và mặc dù chúng có những lợi ích riêng nhưng không phải ai cũng phải xử lý chúng và hoàn toàn không cần thiết. Nếu bạn muốn tôi nói về một chú thích cụ thể từ thư viện tiêu chuẩn (hoặc, có lẽ, các chú thích như @NotNull và @Nullable, không có trong STL), hãy viết nhận xét - những người dùng tử tế sẽ trả lời bạn ở đó, hoặc Tôi sẽ làm khi tôi nhìn thấy nó. Nếu nhiều người yêu cầu chú thích gì đó thì mình cũng sẽ thêm vào bài viết.

Ứng dụng thực tế của chú thích RUNTIME

Trên thực tế, tôi nghĩ nói lý thuyết thế là đủ: hãy chuyển sang thực hành sử dụng ví dụ về bot. Giả sử bạn muốn viết bot cho một số mạng xã hội. Tất cả các mạng lớn, chẳng hạn như VK, Facebook, Discord, đều có API riêng cho phép bạn viết bot. Đối với các mạng tương tự này, đã có các thư viện bằng văn bản để làm việc với các API, kể cả trong Java. Vì vậy, chúng tôi sẽ không đi sâu vào công việc của bất kỳ API hoặc thư viện nào. Tất cả những gì chúng ta cần biết trong ví dụ này là bot của chúng ta có thể trả lời các tin nhắn được gửi đến cuộc trò chuyện mà bot của chúng ta thực sự nằm trong đó. Tức là, giả sử chúng ta có một lớp MessageListener có hàm:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
Nó có trách nhiệm xử lý tin nhắn nhận được. Tất cả những gì chúng ta cần từ lớp MessageReceuredEvent là chuỗi tin nhắn đã nhận được (ví dụ: “Xin chào” hoặc “Bot, xin chào”). Điều đáng xem xét: trong các thư viện khác nhau, các lớp này được gọi khác nhau. Tôi đã sử dụng thư viện cho Discord. Và vì vậy, chúng tôi muốn làm cho bot phản ứng với một số lệnh bắt đầu bằng “Bot” (có hoặc không có dấu phẩy - hãy tự quyết định: vì lợi ích của bài học này, chúng tôi sẽ cho rằng không nên có dấu phẩy ở đó). Nghĩa là, chức năng của chúng tôi sẽ bắt đầu bằng một cái gì đó như:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
Và bây giờ chúng ta có nhiều lựa chọn để thực hiện lệnh này hoặc lệnh kia. Không còn nghi ngờ gì nữa, trước tiên bạn cần tách lệnh khỏi các đối số của nó, nghĩa là chia nó thành một mảng.
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
            //В случае если просто написать "Бот"
        }
    }
}
Không có cách nào chúng ta có thể tránh được đoạn mã này, bởi vì việc tách lệnh khỏi các đối số luôn là điều cần thiết. Nhưng sau đó chúng ta có một sự lựa chọn:
  • Làm if(command.equalsIngnoreCase("..."))
  • Thực hiện chuyển đổi (lệnh)
  • Thực hiện một số cách xử lý khác ...
  • Hoặc nhờ đến sự trợ giúp của Chú thích.
Và bây giờ chúng ta cuối cùng đã đến phần thực hành của việc sử dụng Chú thích. Hãy xem mã chú thích cho nhiệm vụ của chúng ta (tất nhiên nó có thể khác).
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();

}
Quan trọng! Mỗi tham số được mô tả dưới dạng một hàm (có dấu ngoặc đơn). Chỉ có thể sử dụng các giá trị nguyên thủy, String , Enum làm tham số . Bạn không thể viết List<String> args(); - lỗi. Bây giờ chúng ta đã mô tả Chú thích, hãy tạo một lớp, gọi nó là 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();

    }
}
Điều đáng chú ý là một bất tiện nhỏ: t.c. Hiện chúng ta đang đấu tranh cho tính phổ quát, tất cả các hàm phải có cùng một danh sách các tham số hình thức, vì vậy ngay cả khi lệnh không có đối số thì hàm đó cũng phải có tham số String[] args . Bây giờ chúng ta đã mô tả 3 lệnh: xin chào, tạm biệt, trợ giúp. Bây giờ hãy sửa đổi MessageListener của chúng ta để làm điều gì đó với điều này. Để thuận tiện và tốc độ làm việc, chúng tôi sẽ lưu trữ ngay các lệnh của mình trong 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
                //В случае если просто написать "Бот"
            }
        }
    }
}
Đó là tất cả những gì cần thiết để nhóm của chúng tôi làm việc. Bây giờ, việc thêm một lệnh mới không phải là một if mới, không phải là một trường hợp mới, trong đó số lượng đối số sẽ cần phải được tính toán lại và trợ giúp cũng sẽ phải được viết lại, thêm các dòng mới vào đó. Bây giờ, để thêm lệnh, chúng ta chỉ cần thêm một hàm mới với chú thích @Command trong lớp CommandListener và thế là xong - lệnh được thêm vào, các trường hợp được tính đến, trợ giúp được thêm tự động. Hoàn toàn không thể chối cãi rằng vấn đề này có thể được giải quyết bằng nhiều cách khác. Có, mọi thứ có thể được thực hiện với sự trợ giúp của chú thích/phản ánh đều có thể được thực hiện mà không cần đến chúng, tất nhiên, câu hỏi duy nhất là sự tiện lợi, tối ưu và kích thước mã, dán Chú thích vào bất cứ nơi nào có gợi ý nhỏ nhất rằng nó có thể sử dụng được nó cũng không phải là lựa chọn hợp lý nhất, trong mọi việc bạn cần biết khi nào nên dừng =). Nhưng khi viết API, Thư viện hoặc chương trình có thể lặp lại mã cùng loại (nhưng không hoàn toàn giống nhau), chú thích chắc chắn là giải pháp tối ưu.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION