GetDeclaredAnnotations: примеры (JAVA)

getDeclaredAnnotations: назначение и примеры
Раздел: Аннотации (обработка)
getDeclaredAnnotations: Annotation[]

Описание метода getDeclaredAnnotations

Метод getDeclaredAnnotations() из интерфейса java.lang.reflect.AnnotatedElement возвращает массив аннотаций, непосредственно присутствующих на данном элементе (класс, метод, поле, конструктор, параметр и т. п.). Метод не принимает параметров и всегда возвращает массив типа java.lang.annotation.Annotation[]. Если аннотаций нет, возвращается пустой массив длины 0.

Ключевые особенности поведения:

  • Возвращаются только аннотации, которые непосредственно объявлены на элементе. Аннотации, унаследованные через @Inherited, не включаются в результат этого метода для классов. Для получения унаследованных аннотаций используется getAnnotations().
  • Аннотации с retention RetentionPolicy.CLASS или RetentionPolicy.SOURCE недоступны через рефлексию во время выполнения; в результате они не появятся в возвращаемом массиве.
  • Если аннотация повторяемая (повторное применение @Repeatable), вызов getDeclaredAnnotations() вернёт либо контейнерную аннотацию, либо прокси-объекты, зависящие от способа компиляции; для получения всех повторов удобнее использовать getDeclaredAnnotationsByType(Class<T>) или getAnnotationsByType(Class<T>).
  • Возвращаемый массив - новая копия; модификация массива не влияет на метаданные класса.

Возвращаемые значения:

  • Тип: java.lang.annotation.Annotation[].
  • Пустой массив при отсутствии аннотаций.
  • При наличии аннотаций элементы массива представляют прокси-реализации соответствующих типов аннотаций.

Связанные методы интерфейса AnnotatedElement: getAnnotations(), getDeclaredAnnotation(Class<T>), getDeclaredAnnotationsByType(Class<T>), getAnnotationsByType(Class<T>), isAnnotationPresent(Class<? extends Annotation>). Эти методы полезны для уточнения поиска по типу аннотации и учета наследования.

Простые примеры использования

Ниже приведены небольшие примеры с выводом результата. Код и результат оформлены отдельно.

Пример 1: класс с одной аннотацией

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@interface A { String value() default "x"; }

@A("test")
class C {}

public class Demo1 {
    public static void main(String[] args) {
        java.lang.annotation.Annotation[] anns = C.class.getDeclaredAnnotations();
        System.out.println(java.util.Arrays.toString(anns));
    }
}
[ @A(value=test) ]

Пример 2: унаследованная аннотация и сравнение с getAnnotations()

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Inh { }

@Inh
class Base { }

class Child extends Base { }

public class Demo2 {
    public static void main(String[] args) {
        System.out.println(java.util.Arrays.toString(Child.class.getDeclaredAnnotations()));
        System.out.println(java.util.Arrays.toString(Child.class.getAnnotations()));
    }
}
[]
[ @Inh() ]

Пример 3: повторяемые аннотации и различие методов

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Tags.class)
@interface Tag { String value(); }

@Retention(RetentionPolicy.RUNTIME)
@interface Tags { Tag[] value(); }

@Tag("a")
@Tag("b")
class M {}

public class Demo3 {
    public static void main(String[] args) {
        System.out.println(java.util.Arrays.toString(M.class.getDeclaredAnnotations()));
        System.out.println(java.util.Arrays.toString(M.class.getDeclaredAnnotationsByType(Tag.class)));
    }
}
[ @Tags(value=[@Tag(value=a), @Tag(value=b)]) ]
[ @Tag(value=a), @Tag(value=b) ]

Похожие методы в Java

Краткая сводка похожих методов и их особенностей:

  • getAnnotations() - возвращает аннотации, доступные с учетом наследования (через @Inherited) для классов; в отличие от getDeclaredAnnotations() включает унаследованные.
  • getDeclaredAnnotation(Class<T>) - возвращает первую найденную аннотацию указанного типа, если она непосредственно присутствует, или null иначе; удобнее, когда нужен конкретный тип.
  • getDeclaredAnnotationsByType(Class<T>) - возвращает все аннотации данного типа, непосредственно присутствующие на элементе, с учетом поддержки @Repeatable.
  • isAnnotationPresent(Class<? extends Annotation>) - быстрый булев проверочный метод, который учитывает наследование при вызове isAnnotationPresent на классах.

Выбор зависит от цели: для полного списка прямых аннотаций - getDeclaredAnnotations(), для учета наследования - getAnnotations(), для получения всех экземпляров повторяемой аннотации - getDeclaredAnnotationsByType().

Аналоги в других языках

Краткое сравнение с аналогами в популярных языках и примеры.

  • PHP (attributes, PHP 8+): механизм атрибутов предоставляет Runtime-методы через ReflectionClass::getAttributes(). Отличие: в PHP атрибуты явные объекты-описания, возвращается массив ReflectionAttribute. Пример:
<?
#[Attribute]
class A { public function __construct(public string $v) {} }

#[A("t")]
class C {}

$attrs = (new ReflectionClass(C::class))->getAttributes();
print_r($attrs);
?>
Array (
    [0] => ReflectionAttribute Object (...)
)
  • JavaScript / TypeScript: декораторы (experimental) могут добавлять метаданные через библиотеки (например reflect-metadata). Отличие: стандарт не встроен, требуется явное сохранение метаданных.
// TypeScript + reflect-metadata
import 'reflect-metadata';
function A(val: string) { return Reflect.metadata('a', val); }

@A('x')
class C {}

console.log(Reflect.getMetadata('a', C));
x
  • Python: аннотации для параметров и возвращаемых значений хранятся в __annotations__, а декораторы часто добавляют атрибуты к объектам. Отличие: нет стандартизированного механизма аннотаций как у Java, но декораторы дают гибкость.
def deco(func):
    func._tag = 'x'
    return func

@deco
def f(): pass

print(getattr(f, '_tag', None))
x
  • C#: Reflection предоставляет GetCustomAttributes и GetCustomAttributesData. Поведение похоже на Java, но тип возвращаемых объектов и механика доступа к значениям отличаются (AttributeInstance vs Annotation proxy).
using System;
[Obsolete("test")]
class C { }

class P { static void Main(){
    var attrs = typeof(C).GetCustomAttributes(false);
    Console.WriteLine(attrs.Length);
}}
1
  • Go: теги полей структур доступны через reflect.StructTag и метод reflect.Type.Field(i).Tag.Get("key"). Отличие: аннотации представлены строками в тегах, не отдельными типами.
package main
import (
    "fmt"
    "reflect"
)

type S struct{ F int `json:"f" tag:"x"` }
func main(){
    t := reflect.TypeOf(S{})
    fmt.Println(t.Field(0).Tag.Get("tag"))
}
x

Вывод: у большинства языков есть механизмы метаданных, но форма, доступность во время выполнения и удобство работы отличаются. Java предоставляет строгую модель аннотаций с типами и прокси-объектами, что упрощает типобезопасный доступ к значениям.

Типичные ошибки и их причины

Ниже перечислены распространённые ошибки при работе с getDeclaredAnnotations() и способы диагностирования.

  • Отсутствие аннотаций в результате вызова: чаще всего вызвано тем, что аннотация имеет RetentionPolicy.CLASS или SOURCE. Решение: установить @Retention(RetentionPolicy.RUNTIME) для доступа через рефлексию.
  • Ожидание унаследованных аннотаций: getDeclaredAnnotations() не возвращает унаследованные аннотации. Для учета наследования использовать getAnnotations(). Пример показан в разделе примеров.
  • Проблемы с повторяемыми аннотациями: при использовании контейнерной аннотации вызов может вернуть контейнер, а не отдельные элементы. Для получения всех экземпляров применять методы getDeclaredAnnotationsByType()/getAnnotationsByType().
  • TypeNotPresentException или AnnotationFormatError: возможны, если аннотация или её элемент ссылаются на отсутствующий тип или недопустимый формат. В логах/стек-трейсе будет информация о причине; проверка classpath и корректности объявлений аннотаций поможет устранить проблему.
  • AnnotationTypeMismatchException при обращении к значениям аннотации: возникает, если ожидаемый тип значения не совпадает с реальным (например, массив вместо одиночного значения). Причина часто в неверном объявлении аннотации или неправильной обработке прокси.

Пример ошибки из-за retention:

import java.lang.annotation.*;

@Retention(RetentionPolicy.CLASS)
@interface R { }

@R
class X {}

public class E { public static void main(String[] a){
    System.out.println(X.class.getDeclaredAnnotations().length);
}}
0

Изменения в API и история

Ключевые изменения, связанные с работой аннотаций и методами интерфейса AnnotatedElement:

  • В Java 5 (JDK 1.5) введена система аннотаций и базовые методы рефлексии для работы с ними, включая getDeclaredAnnotations().
  • В Java 8 добавлены методы, облегчающие работу с повторяемыми аннотациями: getAnnotation(Class<T>), getDeclaredAnnotation(Class<T>), getAnnotationsByType(Class<T>), getDeclaredAnnotationsByType(Class<T>). Эти дополнения упростили обработку @Repeatable.
  • В более поздних релизах существенных изменений в семантике getDeclaredAnnotations() не вносилось; развивались сопутствующие механизмы безопасности и оптимизации JVM, влияющие на производительность рефлексии.

Расширенные и редкие сценарии применения

Несколько подробных примеров с пояснениями и результатами.

1) Чтение значений аннотации с полем-типом Class и возможная ошибка при отсутствии типа в classpath

Пример java
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@interface WithClass { Class<?> value(); }

@WithClass(String.class)
class A {}

public class Adv1 {
    public static void main(String[] args) {
        for (Annotation an : A.class.getDeclaredAnnotations()) {
            System.out.println(an);
        }
    }
}
[ @WithClass(value=class java.lang.String) ]

Если в аннотации указывается класс, отсутствующий в classpath, при доступе к аннотации может возникнуть TypeNotPresentException (в старых версиях) или AnnotationFormatError. Это редкая ситуация, но полезная при диагностике проблем с загрузчиками классов.

2) Обработка повторяемых аннотаций на уровне методов и на параметрах

Пример java
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Tags.class)
@interface Tag { String v(); }
@Retention(RetentionPolicy.RUNTIME)
@interface Tags { Tag[] value(); }

class X {
    @Tag(v="a")
    @Tag(v="b")
    void m(@Tag(v="p") String s) {}
}

public class Adv2 {
    public static void main(String[] args) throws Exception {
        java.lang.reflect.Method mm = X.class.getDeclaredMethod("m", String.class);
        System.out.println(java.util.Arrays.toString(mm.getDeclaredAnnotationsByType(Tag.class)));
        java.lang.reflect.Parameter p = mm.getParameters()[0];
        System.out.println(java.util.Arrays.toString(p.getDeclaredAnnotationsByType(Tag.class)));
    }
}
[ @Tag(v=a), @Tag(v=b) ]
[ @Tag(v=p) ]

Пояснение: для методов и параметров удобно использовать методы ...ByType, они всегда дают набор экземпляров указанного типа без необходимости вручную распаковывать контейнер.

3) Сканирование пакета и кеширование аннотаций (оптимизация)

Пример java
// Упрощенный пример сканера
import java.lang.annotation.*;
import java.util.*;

@Retention(RetentionPolicy.RUNTIME)
@interface S { String v(); }

@S("c1") class C1 {}
@S("c2") class C2 {}

public class Scanner {
    private final Map cache = new HashMap<>();
    public Annotation[] getDeclared(Class<?> cl) {
        return cache.computeIfAbsent(cl, k -> k.getDeclaredAnnotations());
    }
    public static void main(String[] a){
        Scanner s = new Scanner();
        System.out.println(Arrays.toString(s.getDeclared(C1.class)));
        System.out.println(Arrays.toString(s.getDeclared(C2.class)));
    }
}
[ @S(v=c1) ]
[ @S(v=c2) ]

Пояснение: при массовой рефлексии имеет смысл кэшировать результаты getDeclaredAnnotations(), так как это уменьшает нагрузку на JVM при многократном обращении.

4) Обработка некорректных значений аннотации: пример с ожиданием массива, а получением одиночного значения

Пример java
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@interface Arr { String[] v(); }

@Arr(v={"a","b"})
class B {}

public class Adv4 {
    public static void main(String[] args) {
        Arr a = B.class.getDeclaredAnnotation(Arr.class);
        System.out.println(java.util.Arrays.toString(a.v()));
    }
}
[a, b]

Пояснение: при несовпадении типов полей аннотации и кода обработки может появиться AnnotationTypeMismatchException. Проверка сигнатуры аннотации на этапе компиляции и аккуратная обработка в рантайме помогает избежать ошибок.

5) Доступ к аннотациям в условиях безопасности (SecurityManager)

В окружениях с ограниченным доступом к рефлексии могут быть ограничения на получение аннотаций; требуется учитывать политики безопасности и права для рефлексивных операций.

джава getDeclaredAnnotations function comments

En
GetDeclaredAnnotations Returns all annotations directly present on this element