Аннотация в Java это метка в коде, которая ничего не делает сама, а хранит метаданные о классе, методе, поле или параметре. Пишется она через собаку (@Override, @Deprecated, @Autowired), объявляется словом @interface, а всю работу выполняет тот, кто эту метку прочитает: компилятор, IDE, фреймворк или ваш собственный код через рефлексию. Два параметра решают судьбу любой аннотации: Retention (до какого этапа она доживет) и Target (над чем ее разрешено ставить).
Кратко
Аннотация это метаданные, а не исполняемый код. Работу делает тот, кто ее читает: компилятор, IDE, фреймворк или ваша программа.
Retention задает срок жизни: SOURCE (исчезает при компиляции), CLASS (лежит в class-файле, но JVM ее не грузит), RUNTIME (доступна во время работы программы). Без @Retention по умолчанию CLASS.
Target задает, над чем аннотацию можно ставить: метод, поле, класс, параметр и другие варианты из ElementType. Без @Target ее можно ставить над любым объявлением.
Через рефлексию видны только RUNTIME-аннотации, читают их методами isAnnotationPresent() и getAnnotation().
Своя аннотация объявляется как public @interface Имя, параметры описываются как методы со скобками, а типы разрешены не любые: примитив, String, Class, enum, другая аннотация и массив из перечисленного.
Практический смысл там, где иначе разрастается цепочка if или switch: новый случай добавляется одной пометкой над методом, а не правкой в трех местах.
Данная статья предназначена для людей, которые никогда не работали с Аннотациями, но хотели бы разобраться, что это и с чем его едят. Если же вы имеете опыт в данной сфере, не думаю, что эта статья как-то расширит ваши знания (да и, собственно, такую цель я не преследую).
Также статья не подходит для тех, кто только начинает изучать язык Java. Если Вы не понимаете что такое Map<> или HashMap<> или не знаете что означает запись static{ } внутри определения класса, либо же никогда не работали с рефлексией, вам рано читать эту статью и пытаться понять, что такое аннотации. Сам по себе этот инструмент не создан для использования новичками, так как требует уже не совсем базовых пониманий взаимодействия классов и объектов (мое мнение) (спасибо комментариям за то, что показали необходимость этой приписки).
Итак, приступим.
Аннотации в Java являются своего рода метками в коде, описывающими метаданные для функции/класса/пакета. Например, всем известная Аннотация @Override, обозначающая, что мы собираемся переопределить метод родительского класса. Да, с одной стороны, можно и без нее, но если у родителей не окажется этого метода, существует вероятность, что мы зря писали код, т.к. конкретно этот метод может и не вызваться никогда, а с Аннотацией @Override компилятор нам скажет, что: "Я не нашел такого метода в родителях... что-то здесь нечисто".
Однако Аннотации могут нести в себе не только смысл "для надежности": в них можно хранить какие-то данные, которые после будут использоваться.
Для начала рассмотрим простейшие аннотации, предоставляемые стандартной библиотекой.
(снова же спасибо комментариям, вначале не подумал что этот блок нужен)
Сначала обсудим, какие бывают аннотации. У каждой есть 2 главных параметра:
Тип хранения (Retention);
Тип объекта над которым она указывается (Target).
Оба задаются мета-аннотациями @Retention и @Target, и оба необязательны, просто умолчания редко совпадают с тем, что нужно. Если @Retention не написать, аннотация получит политику CLASS и до выполнения программы не доживет, а без @Target ее можно будет ставить над любым объявлением. Так это описано в документации Oracle.
Тип хранения
Под "типом хранения" понимается стадия до которой "доживает" наша аннотация внутри класса. Каждая аннотация имеет только один из возможных "типов хранения" указанный в классе RetentionPolicy:
SOURCE - аннотация используется только при написании кода и игнорируется компилятором (т.е. не сохраняется после компиляции). Обычно используется для каких-либо препроцессоров (условно), либо указаний компилятору
CLASS - аннотация сохраняется после компиляции, однако игнорируется JVM (т.е. не может быть использована во время выполнения). Обычно используется для каких-либо сторонних сервисов, подгружающих ваш код в качестве plug-in приложения
RUNTIME - аннотация которая сохраняется после компиляции и подгружается JVM (т.е. может использоваться во время выполнения самой программы). Используется в качестве меток в коде, которые напрямую влияют на ход выполнения программы (пример будет рассмотрен в данной статье)
Тип объекта над которым указывается annotation
Данное описание стоит понимать практически буквально, т.к. в Java аннотации могут указываться над чем угодно (Поля, классы, функции, т.д.) и для каждой аннотации указывается, над чем конкретно она может быть задана. Здесь уже нет правила "что-то одно", аннотацию можно указывать над всем ниже перечисленным, либо же выбрать только нужные элементы перечисления ElementType, в котором сейчас двенадцать констант:
ANNOTATION_TYPE - другая аннотация
CONSTRUCTOR - конструктор класса
FIELD - поле класса
LOCAL_VARIABLE - локальная переменная
METHOD - метод класса
PACKAGE - описание пакета package
PARAMETER - параметр метода public void hello(@Annotation String param){}
TYPE - указывается над классом
TYPE_PARAMETER - параметр типа, например T в объявлении дженерика (с Java 8)
TYPE_USE - любое место, где тип употребляется, например @NotNull String name (с Java 8)
MODULE - объявление модуля (с Java 9)
RECORD_COMPONENT - компонент рекорда (с Java 16)
На момент актуальной версии Java 25 (LTS) стандартная библиотека предоставляет множество встроенных аннотаций. В данной статье мы разберем наиболее часто используемые из них. Полный список можно найти в официальной документации Oracle
@Override
Retention: SOURCE;
Target: METHOD.
Данная аннотация показывает, что метод, над которым она прописана, переопределяет метод супертипа: родительского класса или интерфейса.
Первая аннотация с которой сталкивался каждый начинающий Java-программист, при использовании IDE, которая настойчиво пихает эти @Override. Зачастую учителя с ютуба рекомендуют либо: "сотрите чтобы не мешало", либо: "оставьте не задумываясь зачем оно здесь". На самом деле аннотация более чем полезна: она не только позволяет понять какие методы были определены в этом классе впервые, а какие уже есть у родителей (что бесспорно повышает читаемость вашего кода), но также данная аннотация служит "самопроверкой", что вы не ошиблись при определении переопределяемой функции.
@Deprecated
Retention: Runtime;
Target: CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE.
Данная аннотация указывает на методы, классы или переменные, которые являются "устаревшими" и могут быть убраны в последующих версиях продукта.
С данной аннотацией обычно сталкиваются те, кто читает документацию каких-либо API, либо той же стандартной библиотеки Java. Иногда эту аннотацию игнорируют, т.к. она не вызывает никаких ошибок и в принципе сама по себе сильно жить не мешает. Однако главный посыл, который несет в себе данная аннотация: "мы придумали более удобный метод реализации данного функционала, используй его, не используй старый" - ну, либо же - "мы переименовали функцию, а это так, для легаси оставили..." (что тоже в общем-то неплохо). Короче говоря, если видите @Deprecated - лучше стараться не использовать то, над чем она висит, если в этом нет прям крайней необходимости и, возможно, стоит перечитать документацию, чтобы понять каким образом теперь реализуется задача, выполняемая устаревшим элементом. Например, метод new Date().getYear() помечен устаревшим еще в Java 1.1 и возвращает не год, а год минус 1900. Класс Calendar устаревшим не помечен, но и он проигрывает по читаемости современному API java.time, где тот же год берется так:
LocalDate.now().getYear()
Еще одна деталь: с Java 9 у самой аннотации появились параметры since и forRemoval. Первый показывает, в какой версии элемент объявили устаревшим, второй предупреждает, что его собираются удалить, а не просто не советуют трогать.
@SuppressWarnings
Retention: SOURCE;
Target: любое объявление.
Данная аннотация отключает вывод предупреждений компилятора, которые касаются элемента, над которым она указана, и всего, что внутри этого элемента. В Java 8 у нее был явный список целей (TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE), а начиная с Java 9 @Target у нее убрали совсем, так что ставить ее можно над любым объявлением.
@Retention
Retention: RUNTIME;
Target: ANNOTATION_TYPE;
Данная аннотация задает "тип хранения" аннотации над которой она указана. Да эта аннотация используется даже для самой себя... магия да и только.
@Target
Retention: RUNTIME;
Target: ANNOTATION_TYPE;
Данная аннотация задает тип объекта над которым может указываться создаваемая нами аннотация. Да и она тоже используется для себя же, привыкайте...
Другие аннотации из JDK
На этом список стандартных аннотаций не заканчивается. В актуальных версиях Java активно применяются следующие:
@FunctionalInterface: гарантирует, что в интерфейсе ровно один абстрактный метод, то есть его можно реализовать лямбдой.
@SafeVarargs: убирает предупреждения компилятора при использовании varargs с дженериками.
@Documented: делает так, что аннотация отображается в Javadoc.
@Repeatable: позволяет одной аннотации повторяться несколько раз над одним элементом.
@Native: помечает константу, на которую может ссылаться нативный код. По этой метке инструменты, которые генерируют заголовочные файлы для JNI, понимают, что константу надо вынести в заголовок.
Версии у них разные: @Documented есть еще с Java 5, @SafeVarargs появилась в Java 7, а @FunctionalInterface, @Repeatable и @Native в Java 8. Все они используются по сей день, поэтому полезно о них знать.
Шпаргалка: аннотации из JDK
Все, что разобрано выше, плюс мета-аннотация @Inherited, о которой речь еще не заходила, в одной таблице. Значения Retention и Target взяты из документации Java 25.
Аннотация
Retention
Target
Зачем нужна
@Override
SOURCE
METHOD
Компилятор проверяет, что метод действительно переопределяет метод супертипа
@Deprecated
RUNTIME
почти любое объявление, включая модуль
Помечает устаревший элемент, с Java 9 есть параметры since и forRemoval
@SuppressWarnings
SOURCE
любое объявление
Гасит предупреждения компилятора у элемента и у всего, что внутри него
@FunctionalInterface
RUNTIME
TYPE
Компилятор проверяет, что в интерфейсе ровно один абстрактный метод
@SafeVarargs
RUNTIME
CONSTRUCTOR, METHOD
Автор ручается, что varargs с дженериками используется безопасно
@Retention
RUNTIME
ANNOTATION_TYPE
Мета-аннотация: задает срок жизни другой аннотации
@Target
RUNTIME
ANNOTATION_TYPE
Мета-аннотация: задает, над чем можно ставить другую аннотацию
@Documented
RUNTIME
ANNOTATION_TYPE
Мета-аннотация: аннотация попадает в Javadoc помеченного элемента
@Inherited
RUNTIME
ANNOTATION_TYPE
Мета-аннотация: аннотацию класса видно и у его наследников
@Repeatable
RUNTIME
ANNOTATION_TYPE
Мета-аннотация: разрешает ставить аннотацию над одним элементом несколько раз
@Native
SOURCE
FIELD
Помечает константу, на которую ссылается нативный код
Аннотации из фреймворков и библиотек
Кроме встроенных аннотаций JDK, в современной Java крайне важны аннотации из популярных библиотек и фреймворков:
Spring Framework
@Service, @Component, @Repository: помечают классы для автоматического сканирования и внедрения зависимостей.
@Autowired: внедрение зависимостей.
@Configuration, @Bean: конфигурация приложения.
@GetMapping, @PostMapping: связывают методы с HTTP-маршрутами.
Lombok
@Getter, @Setter: автоматически генерируют геттеры и сеттеры.
@Builder: создает паттерн «строитель».
@Data: объединяет несколько аннотаций для генерации кода.
@NotNull, @Size, @Email: валидация данных.
Эти аннотации широко применяются в реальных проектах и делают код чище, понятнее и короче.
Практическое применение RUNTIME аннотаций
Собственно, думаю, хватит теоретической болтовни: давайте перейдем к практике на примере бота. Собирать будем в три шага:
описать свою аннотацию @Command с параметрами команды;
повесить ее на методы-обработчики в отдельном классе;
один раз собрать все помеченные методы в Map через рефлексию и вызывать нужный по имени команды.
Допустим вы хотите написать бота для какой-то соцсети. У всех крупных сетей, таких как ВК, Facebook, Discord, есть свои API, которые позволяют написать бота. Для этих же сетей есть уже написанные библиотеки для работы с API, на языке Java в том числе. Поэтому не будем углубляться в работу какого-либо API или библиотеки. Все, что нам нужно знать в данном примере, это то, что наш бот умеет реагировать на сообщения, отправленные в чат, в котором, собственно, наш бот находится.
Т.е. допустим, у нас есть класс MessageListener с функцией:
Она отвечает за обработку принятого сообщения. Все что нам нужно от класса MessageReceivedEvent это строка полученного сообщения (например, "Привет" или "Бот, привет"). Стоит учесть: в разных библиотеках эти классы называются по-разному. Я использовал библиотеку для Discord.
И вот мы хотим сделать так, чтобы бот реагировал на какие-то команды, начинающиеся с "Бот" (с запятой или без, решайте сами: для урока предположим, что запятой там быть не должно).
То есть, уже наша функция будет начинаться с чего-то вроде:
publicvoidonMessageReceived(MessageReceivedEvent event){//Убираем чувствительность к регистру (БоТ, бОт и т.д.)String message = event.getMessage().toLowerCase();if(message.startsWith("бот")){}}
И вот теперь перед нами есть множество вариантов реализации той или иной команды. Бесспорно, для начала нужно отделить команду от ее аргументов, т.е. разбить на массив.
publicvoidonMessageReceived(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);//Получили command = "(команда)"; nArgs = {"аргумент1", "аргумент2",..."аргументN"};//Данный массив может быть пустым}catch(ArrayIndexOutOfBoundsException e){//Вывод списка команд или какого-либо сообщения//В случае если просто написать "Бот"}}}
Данного куска кода нам никак не избежать, потому что отделение команды от аргументов нужно всегда. А вот дальше уже у нас есть выбор:
использовать обычный if (command.equalsIgnoreCase("..."));
Со временем switch стал заметно мощнее, только версии тут разные. Стрелочная форма и switch как выражение доехали до релиза в Java 14 (JEP 361), а сопоставление с образцом по типу стало стандартом только в Java 21 (JEP 441). Стрелочная запись выглядит так:
И вот мы наконец дошли до практической части использования Аннотаций.
Шаг 1. Своя аннотация @Command
Давайте рассмотрим код аннотации для нашей задачи (он может отличаться, конечно же).
importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;//Указывает, что наша Аннотация может быть использована//Во время выполнения через Reflection (нам как раз это нужно).@Retention(RetentionPolicy.RUNTIME)//Указывает, что целью нашей Аннотации является метод//Не класс, не переменная, не поле, а именно метод.@Target(ElementType.METHOD)public@interfaceCommand//Описание. Заметим, что перед interface стоит @;{//Команда за которую будет отвечать функция (например "привет");Stringname();//Аргументы команды, использоваться будут для вывода списка командStringargs();//Минимальное количество аргументов, сразу присвоили 0 (логично)intminArgs()default0;//Описание, тоже для спискаStringdesc();//Максимальное число аргументов. В целом не обязательно, но тоже можно использоватьintmaxArgs()defaultInteger.MAX_VALUE;//Показывать ли команду в списке (вовсе необязательная строка, но мало ли, пригодится!)booleanshowInHelp()defaulttrue;//Какие команды будут считаться эквивалентными нашей//(Например для "привет", это может быть "Здаров", "Прив" и т.д.)//Под каждый случай заводить функцию - не рациональноString[]aliases();}
Важно! Каждый параметр описывается как функция (с круглыми скобками). Типы тут разрешены не любые: примитив, String, Class, перечисление, другая аннотация и массив из чего-то перечисленного. Именно поэтому в листинге выше спокойно живет String[] aliases();, а вот List<String> args(); написать нельзя, будет ошибка компиляции. Полный список зафиксирован в спецификации языка, раздел 9.6.1.
Шаг 2. Класс с методами-командами
Теперь, когда мы описали Аннотацию, давайте заведем класс, назовем его CommandListener.
publicclassCommandListener{@Command(name ="привет",
args ="",
desc ="Будь культурным, поздоровайся",
showInHelp =false,
aliases ={"здаров"})publicvoidhello(String[] args){//Какой-то функционал, на Ваше усмотрение.}@Command(name ="пока",
args ="",
desc ="",
aliases ={"удачи"})publicvoidbye(String[] args){// Функционал}@Command(name ="помощь",
args ="",
desc ="Выводит список команд",
aliases ={"help","команды"})publicvoidhelp(String[] args){StringBuilder sb =newStringBuilder("Список команд: \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();}}
Стоит отметить одно небольшое неудобство: т.к. мы сейчас боремся за универсальность, все функции должны иметь одинаковый список формальных параметров, поэтому даже если у команды нет аргументов, у функции должен быть параметр String[] args.
Мы сейчас описали 3 команды: привет, пока, помощь.
Шаг 3. Карта команд и вызов через рефлексию
Теперь давайте модифицируем наш MessageListener так, чтобы он как-то с этим работал. Для удобства и скорости работы, будем сразу хранить наши команды в HashMap, а заполним ее один раз в статическом блоке, при загрузке класса:
publicclassMessageListener{//Map который хранит как ключ команду//А как значение функцию которая будет обрабатывать командуprivatestaticfinalMap<String,Method>COMMANDS=newHashMap<>();//Объект класса с командами (по сути нужен нам для рефлексии)privatestaticfinalCommandListenerLISTENER=newCommandListener();static{//Берем список всех методов в классе CommandListenerfor(Method m :LISTENER.getClass().getDeclaredMethods()){//Смотрим, есть ли у метода нужная нам Аннотация @Commandif(m.isAnnotationPresent(Command.class)){//Берем объект нашей АннотацииCommand cmd = m.getAnnotation(Command.class);//Кладем в качестве ключа нашей карты параметр name()//Определенный у нашей аннотации,//m это переменная, хранящая наш методCOMMANDS.put(cmd.name(), m);//Также заносим каждый элемент aliases//Как ключ указывающий на тот же самый метод.for(String s : cmd.aliases()){COMMANDS.put(s, m);}}}}publicvoidonMessageReceived(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()){//что-то если аргументов меньше чем нужно}elseif(nArgs.length > com.maxArgs()){//что-то если аргументов больше чем нужно}//Через рефлексию вызываем нашу функцию-обработчик//Именно потому что мы всегда передаем nArgs у функции должен быть параметр//String[] args, иначе она просто не будет найдена;//Приведение к Object обязательно: без него массив//разложится на отдельные аргументы
m.invoke(LISTENER,(Object) nArgs);}catch(ArrayIndexOutOfBoundsException e){//Вывод списка команд или какого-либо сообщения//В случае если просто написать "Бот"}}}}
Отдельно стоит сказать про приведение (Object) в вызове m.invoke, потому что тут легко попасться. У метода invoke сигнатура invoke(Object obj, Object... args), то есть аргументы он принимает через varargs. А String[] это одновременно и Object[], поэтому без приведения компилятор передаст наш массив не как один аргумент, а как весь список аргументов: рефлексия решит, что у метода столько параметров, сколько слов было в команде. Метод hello(String[] args) объявляет ровно один параметр, так что вместо вызова прилетит IllegalArgumentException. В документации это прямо названо среди причин исключения: "if the number of actual and formal parameters differ". Приведение к Object заставляет компилятор завернуть массив в Object[] из одного элемента, и метод получает ровно то, что ждет.
Вот собственно и все, что нужно, чтобы наши команды работали. Теперь добавление новой команды это не новый if и не новый case, в которых нужно было бы заново переучесть количество аргументов, также пришлось бы переписывать help, добавляя в него новые строки. Теперь же, чтобы добавить команду, нам нужно просто в классе CommandListener добавить новую функцию с аннотацией @Command, и все: команда добавлена, случаи учтены, help дополнен автоматически.
Когда аннотации оправданы, а когда нет
Абсолютно бесспорно, что данную задачу можно решить множеством других путей. Да, все что можно сделать при помощи аннотаций/рефлексий можно сделать и без них, вопрос лишь в удобстве, оптимальности и размерах кода, конечно же, совать Аннотацию везде где есть малейший намек на то, что получится ее использовать - тоже не самый рациональный вариант, во всем нужно знать меру =). Но при написании API, Библиотек или программ, в которых возможно повторение однотипного (но не совсем одинакового) кода, аннотации - бесспорно оптимальное решение.
Вопросы и ответы
Чем аннотация отличается от комментария?
Комментарий видит только человек, компилятор его выбрасывает. Аннотация это часть кода: она попадает в class-файл, и ее читают компилятор, IDE, фреймворки и сама программа через рефлексию. Поэтому по аннотации можно принимать решения во время работы приложения, а по комментарию нельзя.
Почему моя аннотация не видна через рефлексию?
Скорее всего у нее не тот срок жизни. Через рефлексию видны только аннотации с политикой RUNTIME. Если над объявлением аннотации не написать @Retention, она по умолчанию получит политику CLASS: попадет в class-файл, но JVM ее не загрузит, и isAnnotationPresent вернет false. Нужно явно указать @Retention(RetentionPolicy.RUNTIME).
Обязательно ли писать @Retention и @Target у своей аннотации?
Формально нет, обе мета-аннотации необязательны. Но умолчания редко совпадают с тем, что нужно: без @Retention аннотация не доживет до выполнения программы, а без @Target ее можно будет ставить над любым объявлением, в том числе там, где она бессмысленна. Поэтому в рабочем коде их почти всегда пишут явно.
Какие типы можно использовать у параметров аннотации?
Спецификация языка разрешает примитивы, String, Class, перечисления, другие аннотации и массивы из перечисленного. Коллекции и произвольные объекты запрещены: List на параметре аннотации дает ошибку компиляции. Именно поэтому список синонимов команды хранят в массиве строк, а не в списке.
Замедляют ли аннотации программу?
Сами по себе нет. Аннотации с политикой SOURCE исчезают после компиляции, с политикой CLASS просто лежат в файле. Расходы появляются только там, где RUNTIME-аннотации читают через рефлексию, и заплатить за это можно один раз, если сложить результат в Map при загрузке класса, как сделано в примере с ботом.
Зачем нужна аннотация, если можно обойтись switch или цепочкой if?
Обойтись можно всегда, вопрос в цене изменений. С switch каждая новая команда это новая ветка, новая правка в проверке аргументов и новая строка в справке. С аннотацией новая команда это один метод с пометкой, а карта команд, проверка аргументов и справка собираются сами. На двух командах разницы нет, на двадцати она заметна.
Редко комменты читаю, извините. Скажем так, начинал еще году в 2015 когда курсы бесплатные были, а так нет, плагины для майнкрафта редактировал/писал параллельно и научился) Потом уже универ и всё такое
Почему-то у меня криво отображается страница:
"Например вместо использований new Date().getYear() рекомендуется использовать Calendar.getInstance().get(Calendar.YEAR).<h4>@SuppressWarnings</h4>Retention: SOURCE;
Target: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE
Данная аннотация отключает вывод предупреждений компилятора, которые касаются элемента над которым она указана. Является SOURCE аннотацией указываемой над полями, методами, классами.<h4>@Retention</h4>Retention: RUNTIME;
Target: ANNOTATION_TYPE;
Данная аннотация задает "тип хранения" аннотации над которой она указана. Да эта аннотация используется даже для самой себя... магия да и только.<h4>@Target</h4>Retention: RUNTIME;
Target: ANNOTATION_TYPE;
Данная аннотация задает тип объекта над которым может указываться создаваемая нами аннотация. Да и она тоже используется для себя же, привыкайте..."
Жесть как неудобно читать...
Трабл в редакторе. Каждый раз при редактировании слетают эти тэги, потому что простым смертным они недоступны ☹️
Написал в саппорт чтобы люди имеющие доступ поправили, жду
private static final Map<String, Method> commands = new HashMap<>();
private static final CommandListener listener = new CommandListener();
.....
не хочу показаться занудным, но, раз уж final, то COMMANDS и LISTENER
Ну, как бы мне тоже не хотелось показаться занудным, но стоит отличать final поля, которые были отмечены данным ключевым словом, чтобы избежать изменения значения разработчиками в последующем - или для инициализации в конструторе (любом другом блоке, обязательно входящем в этап инициализации объекта), от тех final полей, которые были придуманы в качестве «свойств»: что-то вроде enum или properties.
В конце концов, если переменная действительно не меняет своё значение на протяжении всей жизни объекта, то хорошим тоном будет сказать компилятору, ай-да, у нас тут постоянное значение - сделай так, чтобы после компиляции оно подставилось всюду вместо имени переменной.
При этом, хорошим тоном почему-то никто не считает помечать final локальные переменные, которые зачастую также не меняют своё значение
Всё, что я написал выше - конечно, хорошо, но а где пруфы, если это всего лишь моё мнение?
Предлагаю рассмотреть class String (Java 1.17). С ходу же можно обратить внимание на поля
privatefinalbyte coder;privatefinalbyte[] value;
Ну, здесь явно можно заменить lower camel case.
Конечно, класс String вряд ли нам что-то докажет, поэтому давайте посмотрим с спецификацию языка Java версии 1.17
https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.12.4
Стоит обратить внимание на пример кода, где объявляется переменная origin. Как раз спеки, как никогда готовы задавать code style.
Ну и последнее, куда можно глянуть - на нововведения records (record - запись), где каждое поле представляет из себя final, но по-прежнему именуется, придерживаясь стиля lower camel case.
В заключении можно отметить лишь то, что большинство «хорошего» приходит лишь на практике, ибо читать спеки, конечно же, ну, никто не будет...
(:ArbyziK:), автор коммента прав, приватные static поля помечаются в верхнем регистре и через snake_case, тут дело не в final вовсе =) У каждого языка своя культура, например в C# все приватные поля/методы начинаются с "_" ( _var, _method), в Java такое, как правило, не используется, хотя никто и не запрещает
Вообще обычно помечают, по крайней мере сколько я исходники более-менее нормальных разработчиков ковырял, везде апер для статических констант. Но опять же, спеки спеками, они больше рекомендательные, а там, каждый... танцует... как он хочет
У нас явно не deeply immutable, посему оно и не UPPER_SNAKE, вот тут подробнее про это:
https://google.github.io/styleguide/javaguide.html#s5.2.4-constant-names
что-то часто в последнее время встречаю начало последнего абзаца в материале "Абсолютно бесспорно, что данную задачу можно решить множеством других путей. " а это просто учите, но пользоваться им скорее всего не будете )))
Добро пожаловать в программирование!) Любую задачу можно решить сотней разных способов. Зависит от предпочтений самого разработчика, сроков, культуры написания принятой в компании и т.д. Иногда, банально, от целесообразности. Если опираться на пример - будь у вас 100 команд, метод из статьи куда лучше подойдет чем огромный switch-case и уж тем более if-else
Тут дело не в том будешь ли ТЫ ее использовать. Важное правило: ты учишь это не для того чтобы самому использовать, а для того чтобы понимать других.
Бывает что ты залазишь в чужой API с которым тебе надо работать, а там опа! и своя кастомная аннотация кое-где используется.
А зачем? Язык так представляет строку при компиляции. Проще вызвать гетеры этих параметров. Либо, если прям очень нужно именно вывести в таком виде как у Вас, то только