JavaRush /Java Blog /Random-KO /자바 @주석. 그것은 무엇이며 어떻게 사용합니까?
SemperAnte
레벨 4
Донецк

자바 @주석. 그것은 무엇이며 어떻게 사용합니까?

Random-KO 그룹에 게시되었습니다
이 글은 Annotation을 사용해 본 적이 없지만 그것이 무엇인지, 무엇과 함께 사용되는지 이해하고 싶은 사람들을 위한 것입니다. 이 분야에 대한 경험이 있다면 이 기사가 어떻게든 귀하의 지식을 확장할 것이라고 생각하지 않습니다(사실 저는 그러한 목표를 추구하지 않습니다). 또한 이 기사는 이제 막 Java 언어를 배우기 시작한 사람들에게는 적합하지 않습니다. Map<> 또는 HashMap<> 이 무엇인지 이해하지 못하거나 클래스 정의 내의 static{ } 항목이 무엇을 의미하는지 모르 거나 리플렉션 작업을 해본 적이 없다면 이 기사를 읽기에는 아직 이르다. 주석이 무엇인지 이해하려고 노력하십시오. 이 도구 자체는 초보자가 사용하도록 만들어진 것이 아닙니다. 클래스와 개체의 상호 작용에 대한 완전히 기본적인 이해가 필요하지 않기 때문입니다 (내 의견)(이 포스트스크립트의 필요성을 보여주는 의견에 감사드립니다). 자바 @주석.  그것은 무엇이며 어떻게 사용합니까?  - 1그럼 시작해 보겠습니다. Java의 주석은 함수/클래스/패키지에 대한 메타데이터를 설명하는 코드의 일종의 레이블입니다. 예를 들어, 부모 클래스의 메서드를 재정의할 것임을 나타내는 잘 알려진 @Override 주석이 있습니다. 예, 한편으로는 그것 없이도 가능하지만 부모가 이 방법을 가지고 있지 않다면 우리가 코드를 헛되이 작성했을 가능성이 있습니다. 이 특정 메서드는 절대 호출되지 않을 수 있지만 @Override 주석을 사용하면 컴파일러는 "부모에게서 그런 메서드를 찾지 못했습니다... 여기에 뭔가 더러운 것이 있습니다."라고 알려줍니다. 그러나 주석은 "신뢰성"이라는 의미 이상의 의미를 전달할 수 있습니다. 주석은 나중에 사용할 일부 데이터를 저장할 수 있습니다.

먼저 표준 라이브러리에서 제공하는 가장 간단한 주석을 살펴보겠습니다.

(댓글 덕분에 처음에는 이 블록이 필요하다고 생각하지 않았습니다.) 먼저 주석이 무엇인지 논의해 보겠습니다. 각각에는 2개의 주요 필수 매개변수가 있습니다.
  • 보관 유형(보존);
  • 표시된 개체의 유형(대상)입니다.

저장 유형

"저장 유형"이란 주석이 클래스 내에서 "생존"하는 단계를 의미합니다. 각 주석에는 RetentionPolicy 클래스 에 지정된 가능한 "보존 유형" 중 하나만 있습니다 .
  • SOURCE - 주석은 코드를 작성할 때만 사용되며 컴파일러에서는 무시됩니다(즉, 컴파일 후에는 저장되지 않습니다). 일반적으로 모든 전처리기(조건부) 또는 컴파일러에 대한 명령에 사용됩니다.
  • CLASS - 주석은 컴파일 후에도 유지되지만 JVM에서는 무시됩니다(즉, 런타임에 사용할 수 없음). 일반적으로 코드를 플러그인 애플리케이션으로 로드하는 타사 서비스에 사용됩니다.
  • RUNTIME은 컴파일 후 저장되고 JVM에 의해 로드되는 주석입니다(즉, 프로그램 자체 실행 중에 사용할 수 있음). 프로그램 실행에 직접적인 영향을 미치는 코드의 표시로 사용됩니다(예는 이 기사에서 논의됩니다).

위에 표시된 개체의 유형

이 설명은 거의 문자 그대로 받아들여야 합니다. 왜냐하면... Java에서 주석은 무엇이든(필드, 클래스, 함수 등)에 대해 지정할 수 있으며 각 주석에 대해 정확히 무엇에 대해 지정할 수 있는지 표시됩니다. 여기에는 더 이상 "한 가지" 규칙이 없습니다. 아래 나열된 모든 항목 위에 주석을 지정하거나 ElementType 클래스 의 필수 요소만 선택할 수 있습니다 .
  • ANNOTATION_TYPE - 또 다른 주석
  • CONSTRUCTOR - 클래스 생성자
  • FIELD - 클래스 필드
  • LOCAL_VARIABLE - 지역 변수
  • METHOD - 클래스 메소드
  • PACKAGE - 패키지 패키지 에 대한 설명
  • PARAMETER - 메서드 매개변수 public void hello(@Annontation String param){}
  • TYPE - 클래스 위에 표시됨
전체적으로 Java SE 1.8부터 표준 언어 라이브러리는 10개의 주석을 제공합니다. 이 기사에서 우리는 그 중 가장 일반적인 것을 살펴볼 것입니다(누가 이 모든 것에 관심이 있습니까? Javadoc에 오신 것을 환영합니다 ):

@우세하다

보존: 소스; 대상: 방법. 이 주석은 작성된 메서드가 상위 클래스에서 상속되었음을 보여줍니다. @Override를 지속적으로 푸시하는 IDE를 사용할 때 모든 초보 Java 프로그래머가 접하게 되는 첫 번째 주석입니다. 종종 YouTube 교사는 "방해가 되지 않도록 삭제하세요." 또는 "왜 있는지 궁금해하지 말고 그대로 두세요."라고 권장합니다. 실제로 주석은 매우 유용합니다. 이 주석을 사용하면 이 클래스에 처음으로 정의된 메서드와 상위 클래스가 이미 가지고 있는 메서드(확실히 코드의 가독성이 높아짐)를 이해할 수 있을 뿐만 아니라 이 주석도 사용할 수 있습니다. 오버로드된 기능을 정의할 때 실수하지 않았는지 확인하는 "자체 점검" 역할을 합니다.

@더 이상 사용되지 않음

보존: 런타임; 대상: CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE. 이 주석은 "더 이상 사용되지 않으며" 제품의 향후 버전에서 제거될 수 있는 메소드, 클래스 또는 변수를 식별합니다. 이 주석은 일반적으로 API 문서나 동일한 표준 Java 라이브러리를 읽는 사람들이 접하게 됩니다. 때때로 이 주석은 다음과 같은 이유로 무시됩니다. 오류가 발생하지 않으며 원칙적으로 그 자체로는 삶에 큰 영향을 미치지 않습니다. 그러나 이 주석이 전달하는 주요 메시지는 "우리는 이 기능을 구현하기 위한 더 편리한 방법을 찾았습니다. 이를 사용하고 이전 기능을 사용하지 마십시오." 또는 그렇지 않으면 "함수 이름을 바꿨지만 이것은 그렇다면 우리는 그것을 유산으로 남겨두었습니다...”(이 역시 일반적으로 나쁘지 않습니다). 즉, @Deprecated가 표시되면 꼭 필요한 경우가 아니면 계속해서 사용하지 않는 것이 더 좋으며, 더 이상 사용되지 않는 요소가 수행하는 작업이 현재 어떻게 구현되는지 이해하기 위해 문서를 다시 읽어 볼 가치가 있습니다. 예를 들어 new Date().getYear() 를 사용하는 대신 Calendar.getInstance().get(Calendar.YEAR) 을 사용하는 것이 좋습니다 .

@SuppressWarnings

보존: 소스; 대상: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE 이 주석은 지정된 요소와 관련된 컴파일러 경고의 출력을 비활성화합니다. 필드, 메소드, 클래스 위에 표시된 SOURCE 주석입니다.

@보유

보존: 런타임; 대상: ANNOTATION_TYPE; 이 주석은 위에 지정된 주석의 "저장 유형"을 지정합니다. 예, 이 주석은 그 자체로도 사용됩니다... 마술이고 그게 전부입니다.

@표적

보존: 런타임; 대상: ANNOTATION_TYPE; 이 주석은 우리가 생성한 주석이 표시될 수 있는 객체의 유형을 지정합니다. 네, 그리고 그것은 여러분 자신을 위해서도 사용됩니다. 익숙해지세요... 저는 이것이 Java 라이브러리의 표준 주석에 대한 소개를 완료할 수 있는 곳이라고 생각합니다. 나머지는 거의 사용되지 않으며 고유한 이점이 있지만 모든 사람이 이를 처리해야 하는 것은 아니며 완전히 불필요합니다. 표준 라이브러리의 특정 주석(또는 STL에 포함되지 않은 @NotNull 및 @Nullable과 같은 주석)에 대해 이야기하기를 원하는 경우 댓글을 작성하세요. 친절한 사용자가 답변을 드릴 것입니다. 내가 그것을 볼 때 나는 것이다. 많은 분들이 어떤 주석을 요청하시면 글에도 추가하겠습니다.

RUNTIME 주석의 실제 적용

사실 이론적인 잡담은 이 정도로 충분하다고 생각합니다. 이제 봇의 예를 사용하여 연습해 보겠습니다. 일부 소셜 네트워크용 봇을 작성한다고 가정해 보겠습니다. VK, Facebook, Discord와 같은 모든 주요 네트워크에는 봇을 작성할 수 있는 자체 API가 있습니다. 이러한 동일한 네트워크에는 Java를 포함하여 API 작업을 위한 라이브러리가 이미 작성되어 있습니다. 따라서 우리는 API나 라이브러리의 작업을 조사하지 않을 것입니다. 이 예에서 우리가 알아야 할 것은 봇이 실제로 위치한 채팅에 전송된 메시지에 봇이 응답할 수 있다는 것입니다. 즉, 함수가 포함된 MessageListener 클래스가 있다고 가정해 보겠습니다.
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
수신된 메시지를 처리하는 역할을 담당합니다. MessageReceivedEvent 클래스 에서 필요한 것은 수신된 메시지의 문자열(예: "Hello" 또는 "Bot, hello")뿐입니다. 고려해 볼 가치가 있습니다. 다른 라이브러리에서는 이러한 클래스가 다르게 호출됩니다. 디스코드용 라이브러리를 사용했습니다. 그래서 우리는 봇이 "Bot"으로 시작하는 일부 명령에 반응하도록 만들고 싶습니다(쉼표 유무에 관계없이 스스로 결정하십시오. 이 단원에서는 거기에 쉼표가 없어야 한다고 가정합니다). 즉, 우리 함수는 다음과 같이 시작됩니다.
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();를 쓸 수 없습니다. - 오류. 이제 Annotation을 설명했으므로 클래스를 만들어 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 매개변수가 있어야 합니다 . 이제 hello, bye, help라는 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
                //В случае если просто написать "Бот"
            }
        }
    }
}
이것이 우리 팀이 작업하는 데 필요한 전부입니다. 이제 새 명령을 추가하는 것은 인수 수를 다시 계산해야 하고 도움말도 다시 작성하여 새 줄을 추가해야 하는 새 사례가 아닌 새로운 경우가 아닙니다. 이제 명령을 추가하려면 CommandListener 클래스에 @Command 주석이 있는 새 함수를 추가하기만 하면 됩니다. 명령이 추가되고 사례가 고려되며 도움말이 자동으로 추가됩니다. 이 문제가 다른 여러 가지 방법으로 해결될 수 있다는 것은 절대적으로 부인할 수 없습니다. 예, 주석/리플렉션의 도움으로 수행할 수 있는 모든 작업은 주석/리플렉션 없이 수행할 수 있습니다. 유일한 문제는 편의성, 최적성 및 코드 크기입니다. 물론 사용이 가능하다는 힌트가 조금이라도 있는 곳에 주석을 붙입니다. 또한 언제 중지해야 하는지 알아야 할 모든 것에서 가장 합리적인 옵션도 아닙니다 =). 그러나 동일한 유형(정확히 동일하지는 않음) 코드를 반복할 수 있는 API, 라이브러리 또는 프로그램을 작성할 때 주석은 의심할 여지 없이 최적의 솔루션입니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION