Как система типов Java объясняет запрет использовать параметризованный тип как тип элемента аннотации?
Параметризованный тип нельзя использовать как тип элемента аннотации, потому что Java разрешает для элементов аннотаций только ограниченный набор типов, представимых в формате class-файла: примитивы, String, Class, перечисления, другие аннотации и массивы этих типов. Параметризации вроде List<String> в этот набор не входят; после стирания типов их аргументы недоступны как часть runtime-представления значения аннотации.
Generics появились в Java 5 как средство статической типобезопасности, прежде всего для коллекций, с сохранением совместимости существующего байткода. Для этого Java использует стирание типов, поэтому параметры String и Integer у List не становятся частью обычного runtime-типа объекта.
Аннотации проектировались как структурированные метаданные, которые компилятор и инструменты могут прочитать из class-файла. Их значения должны иметь заранее ограниченный и однозначно представимый формат, а не зависеть от недоступной во время выполнения параметризации.
Если бы элемент аннотации принимал произвольный параметризованный тип, возникли бы вопросы о том, что именно должно храниться в class-файле: сама параметризация, стёртый тип или дополнительная информация о типовых аргументах. При стирании типов runtime не может надёжно трактовать List<String> как отличный от List<Integer> тип.
Поэтому декларация элемента аннотации ограничивается специальными типами. Это не означает, что параметризованные объекты нельзя использовать в коде рядом с аннотациями: ограничен именно тип значения элемента аннотации, а не произвольные поля или параметры Java-программ.
Допустимые типы элементов аннотаций определяются правилами Java: примитивный тип, String, Class, enum, тип аннотации либо массив одного из этих типов. Обобщённый класс или интерфейс, например List<String>, не является допустимым типом элемента.
Class<?> здесь допустим, поскольку тип элемента аннотации — специальный тип Class; wildcard является параметризацией самого объекта Class, но не делает декларацию элемента произвольным обобщённым типом. На практике class-литерал также не выражает параметризованный тип: нельзя получить class-литерал именно для List<String>, поскольку во время выполнения существует только класс List.
Главный компромисс такой: аннотации имеют простой и надёжно сериализуемый формат, но не могут напрямую хранить сложные параметризованные значения. Если нужно описать типовую информацию вроде List<String>, её обычно представляют строкой, отдельными элементами аннотации, enum-значениями или специальной структурой метаданных, которую обрабатывает фреймворк.
Предположим, фреймворк должен пометить обработчик и указать модель ответа. Вариант с элементом Class<?> model() позволяет передать конкретный класс модели и безопасно прочитать его через reflection. Попытка объявить элементом List<String> не компилируется, а передача строки вроде "List<String>" теряет проверку компилятором и требует собственного парсинга.
Можно было бы хранить тип модели и список её параметров раздельно, например через model = User.class и массив строк. Это гибче, но строки легко ошибочно написать и сложнее валидировать. Поэтому для фиксированной модели выбирают Class<?>, а для сложной generic-сигнатуры используют отдельный типизированный механизм метаданных фреймворка, а не тип элемента аннотации.
Результат — аннотация остаётся совместимой с моделью class-файла, а проверка сложной параметризации выполняется специализированным кодом там, где доступны дополнительные метаданные.
Class<String> допустимым типом элемента аннотации?Нет. В декларации элемента разрешён тип Class, а не произвольная параметризация Class<T>. Запись Class<?> часто встречается в обычном Java-коде и корректна как тип поля или параметра метода, но элемент аннотации объявляется через специальный аннотационный тип Class.
При использовании аннотации передаётся class-литерал, например String.class. Он представляет объект Class<String> с точки зрения общего Java API, но синтаксис и формат значения аннотации оперируют самим типом Class, без сохранения параметра String как части метаданных.
Да, например хранить строковое значение "java.util.List<java.lang.String>". Однако компилятор не проверяет, что строка является корректным или доступным типовым описанием, а runtime не превращает её автоматически в Type.
Такой подход переносит ответственность на обработчик аннотации: он должен разобрать строку, проверить классы и обработать ошибки. Это допустимо для конфигурационных форматов, но хуже типизированного Java-кода.
Потому что reflection может читать generic-сигнатуры объявлений классов, методов и полей, если компилятор записал их в class-файл. Это отдельные метаданные объявления, а не параметризованные значения элементов аннотации.
Например, поле может иметь generic-сигнатуру List<String> и одновременно быть помечено аннотацией. Reflection способен получить параметр String у поля, но это не означает, что сама аннотация может иметь элемент типа List<String>.