Аннотация в 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 с функцией:
public class MessageListener
{
public void onMessageReceived(MessageReceivedEvent event)
{
}
}
Она отвечает за обработку принятого сообщения. Все что нам нужно от класса MessageReceivedEvent это строка полученного сообщения (например, "Привет" или "Бот, привет"). Стоит учесть: в разных библиотеках эти классы называются по-разному. Я использовал библиотеку для Discord.
И вот мы хотим сделать так, чтобы бот реагировал на какие-то команды, начинающиеся с "Бот" (с запятой или без, решайте сами: для урока предположим, что запятой там быть не должно).
То есть, уже наша функция будет начинаться с чего-то вроде:
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);
//Получили command = "(команда)"; nArgs = {"аргумент1", "аргумент2",..."аргументN"};
//Данный массив может быть пустым
}
catch (ArrayIndexOutOfBoundsException e)
{
//Вывод списка команд или какого-либо сообщения
//В случае если просто написать "Бот"
}
}
}
Данного куска кода нам никак не избежать, потому что отделение команды от аргументов нужно всегда. А вот дальше уже у нас есть выбор:
использовать обычный if (command.equalsIgnoreCase("..."));
Со временем switch стал заметно мощнее, только версии тут разные. Стрелочная форма и switch как выражение доехали до релиза в Java 14 (JEP 361), а сопоставление с образцом по типу стало стандартом только в Java 21 (JEP 441). Стрелочная запись выглядит так:
switch (command) {
case "start" -> System.out.println("Стартуем!");
case "stop" -> System.out.println("Останавливаемся!");
default -> System.out.println("Неизвестная команда");
}
И вот мы наконец дошли до практической части использования Аннотаций.
Шаг 1. Своя аннотация @Command
Давайте рассмотрим код аннотации для нашей задачи (он может отличаться, конечно же).
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//Указывает, что наша Аннотация может быть использована
//Во время выполнения через Reflection (нам как раз это нужно).
@Retention(RetentionPolicy.RUNTIME)
//Указывает, что целью нашей Аннотации является метод
//Не класс, не переменная, не поле, а именно метод.
@Target(ElementType.METHOD)
public @interface Command //Описание. Заметим, что перед interface стоит @;
{
//Команда за которую будет отвечать функция (например "привет");
String name();
//Аргументы команды, использоваться будут для вывода списка команд
String args();
//Минимальное количество аргументов, сразу присвоили 0 (логично)
int minArgs() default 0;
//Описание, тоже для списка
String desc();
//Максимальное число аргументов. В целом не обязательно, но тоже можно использовать
int maxArgs() default Integer.MAX_VALUE;
//Показывать ли команду в списке (вовсе необязательная строка, но мало ли, пригодится!)
boolean showInHelp() default true;
//Какие команды будут считаться эквивалентными нашей
//(Например для "привет", это может быть "Здаров", "Прив" и т.д.)
//Под каждый случай заводить функцию - не рационально
String[] aliases();
}
Важно! Каждый параметр описывается как функция (с круглыми скобками). Типы тут разрешены не любые: примитив, String, Class, перечисление, другая аннотация и массив из чего-то перечисленного. Именно поэтому в листинге выше спокойно живет String[] aliases();, а вот List<String> args(); написать нельзя, будет ошибка компиляции. Полный список зафиксирован в спецификации языка, раздел 9.6.1.
Шаг 2. Класс с методами-командами
Теперь, когда мы описали Аннотацию, давайте заведем класс, назовем его 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();
}
}
Стоит отметить одно небольшое неудобство: т.к. мы сейчас боремся за универсальность, все функции должны иметь одинаковый список формальных параметров, поэтому даже если у команды нет аргументов, у функции должен быть параметр String[] args.
Мы сейчас описали 3 команды: привет, пока, помощь.
Шаг 3. Карта команд и вызов через рефлексию
Теперь давайте модифицируем наш MessageListener так, чтобы он как-то с этим работал. Для удобства и скорости работы, будем сразу хранить наши команды в HashMap, а заполним ее один раз в статическом блоке, при загрузке класса:
public class MessageListener
{
//Map который хранит как ключ команду
//А как значение функцию которая будет обрабатывать команду
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))
{
//Берем объект нашей Аннотации
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, иначе она просто не будет найдена;
//Приведение к 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). С ходу же можно обратить внимание на поля
private final byte coder;
private final byte[] 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 с которым тебе надо работать, а там опа! и своя кастомная аннотация кое-где используется.
А зачем? Язык так представляет строку при компиляции. Проще вызвать гетеры этих параметров. Либо, если прям очень нужно именно вывести в таком виде как у Вас, то только
Также статья не подходит для тех, кто только начинает изучать язык Java. Если Вы не понимаете что такое Map<> или HashMap<> или не знаете что означает запись static{ } внутри определения класса, либо же никогда не работали с рефлекцией
->рефлексией
Тип объекта над которым указывается
Данное описание стоит понимать практически буквально, т.к. в Java аннотации могут указываться над чем угодно (Поля, класса,
->классы
Мне было интересно. Автору - спасибо!
Для понимания материала, требуется хотя бы базовое представление об аннотациях.
Интересно, а на JavaRush есть какое-то задание по написанию своего бота?
Что-то я не понял, как MessageListner будет понимать какой метод вызвать? Если все методы аннотированы одной аннотацией, только присвоение переменных разное, что-то не понял где проходит разбор по присвоенным переменным или как вообще это происходит?
все методы хранятся в мапе commands, метод соответственно будет получен после обработки входящего сообщения и вычленения названия метода ( String command = args[1].toLowerCase();) Method m = commands.get(command); Сама аннотация это просто метка в коде которая позволяет легко расширять логику программы - просто добавив в класс CommandListener очередной блок @Command(name = "newCommand".....) {function}
для меня, который первый раз услышал об аннотациях и полез прочитать про них, ВООБЩЕ бесполезная статья! Я по прежнему что-то слышал об аннотациях, ну плюс что-то не понятное прочитал... что прочитал, что радио послушал =)
public class Animal {
public void speak() {
}
}
public class Cat extends Animal {
@Override // Аннотация, говорящая о том, что этот метод переопределяет одноимённый метод родительского класса
public void speak() {
System.out.println("Meow.");
}
@Deprecated // Аннотация, говорящая о том, что этот метод устарел и будет удалён в ближайшее время
public boolean soundsGood() {
return true;
}
}
Аннотации, применяемые к исходному коду:
@Override - аннотация-маркер, которая может применяться только к методам. Метод, аннотированный как @Override, должен переопределять метод супер класса. @Override — проверяет, переопределён ли метод. Вызывает ошибку компиляции / интерпретации, если метод не найден в родительском классе или интерфейсе;
@Deprecated — отмечает, что метод устарел и не рекомендуется к использованию. Предполагается, что по каким-то причинам этот метод пока оставлен, но будет удалён в будущих версиях. Вызывает предупреждение компиляции, если метод используется;
@SuppressWarnings — указывает компилятору подавить предупреждения компиляции, определённые в параметрах аннотации;
@SafeVarargs — указывает, что никакие небезопасные действия, связанные с параметром переменного количества аргументов, недопустимы. Применяется только к методам и конструкторам с переменным количеством аргументов, которые объявлены как static или final.
Почти своевременно отвечу)) Аннотации описываются как и любой другой класс. Можно описать положив аннотацию в свой файлик с названием Command.java (если глядеть на пример из статьи), либо же можно создать анонимный (вложенный) класс внутри другого класса.
public class Test
{
public @interface TestAnnotation
{
String value();
}
}
Я рекомендую использовать вариант с отдельным файлом т.к. так повышается читаемость всего проекта
Автор пытается рассказать людям, "которые никогда не работали с Аннотациями", как создавать собственные Аннотации?
Как-то преждевременно, на мой взгляд.
Все хорошо и понятно разжевано. Пробовал читать про аннотации у Шилдта - не сразу понял. Тут же более яркий пример и более понятно в каких случаях удобно использовать такой инструмент. Но единственное замечание - с русским у автора плохо. Много ошибок... Исправьте, пожалуйста.
Тут рассчитано не новичка в программировании, а на новичка в аннотациях, эта лекция дает на 29 уровне коллекций. А у кого-то это уже по 40й уровень, эти люди уже вдоволь про обучении использовали и аннотации и тем более рефлексию.
Нехватает про использование множества аннотаций над элементов (одной и той же, собираемой в группу).
Очень нехватает разбора того, как это всё работает. Например, как тут: How do annotations work internally.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ