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

Разбор поведения getAnnotation в Java
Раздел: Аннотации (обработка)
getAnnotation(Class annotationClass): T

Общее описание метода getAnnotation

Метод getAnnotation служит для получения экземпляра аннотации, связанной с элементом отражения (элемент реализует интерфейс java.lang.reflect.AnnotatedElement). На практике этот метод доступен через классы Class, Method, Field, Constructor, Parameter и другие реализующие AnnotatedElement.

Подпись (основные варианты):

<A extends java.lang.annotation.Annotation> A getAnnotation(Class<A> annotationClass)

Аргументы:

  • annotationClass - класс аннотации (Class<A>). Если null, будет выброшено java.lang.NullPointerException.

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

  • Экземпляр аннотации типа A, если аннотация находится на элементе (включая случаи наследования для классов, помеченных @Inherited, когда вызывается на Class).
  • null, если аннотация не найдена или имеет RetentionPolicy отличное от RUNTIME (например, CLASS или SOURCE).

Особенности поведения:

  • Для классов поиск может учитывать наследование аннотаций, помеченных @Inherited: Class.getAnnotation вернет аннотацию от суперкласса, если она отмечена @Inherited и отсутствует в текущем классе. Для методов, полей и параметров наследование аннотаций автоматически не выполняется.
  • Для повторяемых аннотаций (repeatable) getAnnotation возвращает только одну аннотацию указанного типа. Для получения всех экземпляров используется getAnnotationsByType.
  • Если аннотация имеет RetentionPolicy.RUNTIME, она доступна через рефлексию. При политике CLASS или SOURCE данные аннотации недоступны во время выполнения.
  • getAnnotation не создает новых классов аннотаций вручную: возвращается прокси-объект, реализующий интерфейс аннотации, с доступом к значениям полей аннотации.

Связанные методы, влияющие на поведение:

  • getAnnotations() - возвращает все аннотации, включая унаследованные для классов.
  • getDeclaredAnnotation(Class<A>) - возвращает аннотацию, непосредственно объявленную на элементе (без учета наследования).
  • getAnnotationsByType(Class<A>) - учитывает контейнер для repeatable-annotations и возвращает массив всех вхождений.

Короткие примеры применения

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

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@interface Info { String value(); }

@Info("DemoClass")
class Demo {}

public class Test1 {
    public static void main(String[] args) {
        Info info = Demo.class.getAnnotation(Info.class);
        System.out.println(info);
        System.out.println(info != null ? info.value() : "null");
    }
}
@Info(value=DemoClass)
DemoClass

Пример 2: аннотация с RetentionPolicy.CLASS недоступна во время выполнения

import java.lang.annotation.*;

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

@Hidden
class C {}

public class Test2 {
    public static void main(String[] args) {
        Object a = C.class.getAnnotation(Hidden.class);
        System.out.println(a);
    }
}
null

Пример 3: отличие getAnnotation и getDeclaredAnnotation с @Inherited

import java.lang.annotation.*;

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

@Mark
class Parent {}

class Child extends Parent {}

public class Test3 {
    public static void main(String[] args) {
        System.out.println(Child.class.getAnnotation(Mark.class));
        System.out.println(Child.class.getDeclaredAnnotation(Mark.class));
    }
}
@Mark()
null

Пример 4: repeatable-аннотация и getAnnotationsByType

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 X {}

public class Test4 {
    public static void main(String[] args) {
        Tag t = X.class.getAnnotation(Tag.class);
        Tag[] all = X.class.getAnnotationsByType(Tag.class);
        System.out.println(t);
        System.out.println(all.length + ": " + java.util.Arrays.toString(all));
    }
}
@Tag(value=a)
2: [@Tag(value=a), @Tag(value=b)]

Пример 5: метод-аннотация не наследуется при переопределении

import java.lang.annotation.*;

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

class Base {
    @MAnn(v="base")
    public void m() {}
}
class Sub extends Base {
    @Override
    public void m() {}
}

public class Test5 {
    public static void main(String[] args) throws Exception {
        System.out.println(Sub.class.getMethod("m").getAnnotation(MAnn.class));
    }
}
null

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

  • getDeclaredAnnotation(Class<A>) - возвращает аннотацию, объявленную непосредственно на элементе; не учитывает наследование. Предпочтителен при желании избежать унаследованных меток.
  • getAnnotations() - возвращает массив всех аннотаций, видимых для элемента (для классов включает унаследованные аннотации). Удобен при перечислении всех меток.
  • getDeclaredAnnotations() - возвращает все аннотации, явно объявленные на элементе.
  • getAnnotationsByType(Class<A>) - возвращает все вхождения повторяемой аннотации, распаковывая контейнер. Предпочтителен для repeatable-аннотаций.
  • isAnnotationPresent(Class<? extends Annotation>) - булевый индикатор наличия аннотации; эквивалент проверки getAnnotation(...) != null.

Выбор между ними зависит от цели: получение единственного экземпляра (getAnnotation), учет контейнера repeatable (getAnnotationsByType) или строгое чтение только локально объявленных аннотаций (getDeclaredAnnotation / getDeclaredAnnotations).

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

Краткие соответствия и отличия по языкам:

  • C#: System.Reflection.Attribute.GetCustomAttribute / GetCustomAttributes. Возвращает объект Attribute; поведение похоже, но атрибуты доступны по умолчанию при компиляции, механизм наследования управляется атрибутом InheritedAttribute. Пример:
    [System.AttributeUsage(System.AttributeTargets.Class, Inherited=true)]
    class MyAttr : System.Attribute { public string V; public MyAttr(string v){V=v;} }
    
    [MyAttr("A")]
    class P {}
    class C : P {}
    
    // получение
    var a = (MyAttr)typeof(C).GetCustomAttribute(typeof(MyAttr));
    Console.WriteLine(a.V);
    
    A
  • Kotlin: kotlin.reflect.KAnnotatedElement.findAnnotation / annotations. Поведение схоже с Java, но добавлена более удобная интеграция с Kotlin-метаданными. Пример:
    @Target(AnnotationTarget.CLASS)
    @Retention(AnnotationRetention.RUNTIME)
    annotation class Info(val v: String)
    
    @Info("K")
    class A
    
    println(A::class.findAnnotation()?.v)
    
    K
  • Python: в языке аннотации типов и декораторы используются иначе. Для получения метаданных декоратор обычно сохраняет данные в атрибуте функции. Пример:
    def tag(v):
        def d(f):
            f._tag = v
            return f
        return d
    
    @tag('x')
    def f(): pass
    
    print(getattr(f, '_tag', None))
    
    x
  • JavaScript / TypeScript: при использовании reflect-metadata доступно Reflect.getMetadata(metaKey, target, propertyKey). Аннотации реализуются как метаданные; нет встроенного механизма стандартной аннотации как в Java. Пример (TypeScript + reflect-metadata):
    import 'reflect-metadata';
    
    function Tag(v: string){ return (t:any, k?:string)=> Reflect.defineMetadata('tag', v, t, k); }
    
    class A{ @Tag('x') m(){} }
    
    console.log(Reflect.getMetadata('tag', A.prototype, 'm'))
    
    x
  • PHP (8+): ReflectionAttribute / getAttributes. В PHP 8+ есть native attributes; поведение похоже по назначению, синтаксис иной. Пример:
    #[Attribute]
    class Info { public function __construct(public string $v) {} }
    
    #[Info('x')]
    class A {}
    
    $ref = new ReflectionClass(A::class);
    $attr = $ref->getAttributes(Info::class)[0]->newInstance();
    echo $attr->v;
    
    x
  • Go: рефлексия использует теги struct field (reflect.StructTag). Аннотации как в Java отсутствуют; метаданные хранятся в строковых тегах полей. Пример:
    type T struct { X int `json:"x" tag:"v"` }
    
    import "reflect"
    
    var t T
    fmt.Println(reflect.TypeOf(t).Field(0).Tag.Get("tag"))
    
    v
  • SQL, Lua: встроенного механизма аннотаций нет; обычно используются дополнительные схемы, комментарии или внешние метаданные.

В отличие от Java, в ряде языков аннотации реализуются через отдельные библиотеки или имеют иной семантический уровень (например, теги в Go или атрибуты в C#), что влияет на доступность во время выполнения и на поддержку наследования.

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

  • NullPointerException при null аргументе. Вызов getAnnotation(null) вызывает NPE.
    Demo.class.getAnnotation(null);
    Exception in thread "main" java.lang.NullPointerException
        at java.base/java.lang.Class.getAnnotation(Class.java:...)
  • Ожидание аннотации с RetentionPolicy.CLASS. При политике CLASS аннотация недоступна в рантайме, результат null.
    @Retention(RetentionPolicy.CLASS)
    @interface A{}
    
    System.out.println(X.class.getAnnotation(A.class));
    null
  • Непонимание наследования для методов. Для методов getAnnotation не ищет аннотацию в переопределенных версиях автоматически: часто выводится null, если аннотация стоит в базовом методе, а вызов производится на методе из подкласса.
  • Неправильная обработка repeatable-аннотаций. Ожидание, что getAnnotation вернет массив; на самом деле нужен getAnnotationsByType. Пример:
    @Tag("a") @Tag("b") class A{}
    System.out.println(A.class.getAnnotation(Tag.class));
    System.out.println(Arrays.toString(A.class.getAnnotationsByType(Tag.class)));
    
    @Tag(value=a)
    [@Tag(value=a), @Tag(value=b)]

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

  • Java 8 ввела поддержку repeatable аннотаций и связанные методы getAnnotationsByType и getDeclaredAnnotationsByType, что упростило работу с множественными аннотациями одного типа.
  • Ранее методы getAnnotation, getAnnotations и getDeclaredAnnotations существовали в JDK, однако улучшенная поддержка repeatable-аннотаций появилась именно с Java 8.
  • В остальном сигнатуры и семантика getAnnotation оставались стабильными в современных версиях JDK; изменений, ломающих обратную совместимость, не отмечено.

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

Пример: утилита для поиска аннотации метода в иерархии классов и интерфейсов. Полезно при попытке обнаружить аннотацию объявленную в интерфейсе или базовом классе.

Пример java
import java.lang.annotation.*;
import java.lang.reflect.*;

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

interface I { @A(v="iface") void m(); }
class B implements I { public void m(){} }

public class Finder {
    static A findMethodAnnotation(Method m, Class<A> annClass) {
        A a = m.getAnnotation(annClass);
        if (a != null) return a;
        // поиск в интерфейсах
        for (Class<?> inf : m.getDeclaringClass().getInterfaces()) {
            try {
                Method im = inf.getMethod(m.getName(), m.getParameterTypes());
                a = im.getAnnotation(annClass);
                if (a != null) return a;
            } catch (NoSuchMethodException ignored) {}
        }
        // поиск в суперклассах
        Class<?> sc = m.getDeclaringClass().getSuperclass();
        while (sc != null) {
            try {
                Method sm = sc.getMethod(m.getName(), m.getParameterTypes());
                a = sm.getAnnotation(annClass);
                if (a != null) return a;
            } catch (NoSuchMethodException ignored) {}
            sc = sc.getSuperclass();
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        Method m = B.class.getMethod("m");
        System.out.println(findMethodAnnotation(m, A.class));
    }
}
@A(v=iface)

Пояснение: стандартный getAnnotation у метода B.m возвращает null, поскольку аннотация определена в интерфейсе. Представленная логика проходит интерфейсы и суперклассы для нахождения аннотации.

Пример: чтение значений полей аннотации и обработка значений по умолчанию

Пример java
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@interface Conf { String url() default "localhost"; int port() default 80; }

@Conf(port=8080)
class S {}

public class ReadVals {
    public static void main(String[] args) {
        Conf c = S.class.getAnnotation(Conf.class);
        System.out.println(c.url());
        System.out.println(c.port());
    }
}
localhost
8080

Пример: обработка аннотации, примененной к параметру метода

Пример java
import java.lang.annotation.*;
import java.lang.reflect.*;

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

class X { void f(@P(v="p1") String s) {} }

public class ParamAnn {
    public static void main(String[] args) throws Exception {
        Method m = X.class.getDeclaredMethod("f", String.class);
        Annotation[][] pa = m.getParameterAnnotations();
        for (Annotation[] a : pa) System.out.println(java.util.Arrays.toString(a));
    }
}
[@P(v=p1)]

Пример: получение мета-аннотаций (аннотация аннотации)

Пример java
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@interface Meta { }

@Meta
@Retention(RetentionPolicy.RUNTIME)
@interface A { }

@A
class C {}

public class MetaExample {
    public static void main(String[] args) {
        A a = C.class.getAnnotation(A.class);
        if (a != null) {
            Meta m = a.annotationType().getAnnotation(Meta.class);
            System.out.println(m);
        }
    }
}
@Meta()

Пояснение: иногда требуется не только читать саму аннотацию, но и её мета-информацию. Для этого используется annotationType().getAnnotation(...).

джава getAnnotation function comments

En
GetAnnotation Returns the annotation of the specified type