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

Примеры работы isAnnotationPresent
Раздел: Аннотации (обработка)
isAnnotationPresent(Class annotationClass): boolean

Общее описание

Метод isAnnotationPresent принадлежит интерфейсу java.lang.reflect.AnnotatedElement и применяется для проверки наличия аннотации заданного типа на элементе отражения (класс, метод, поле, конструктор, параметр и т.д.). Сигнатура метода выглядит так: boolean isAnnotationPresent(Class<? extends Annotation> annotationClass).

Аргументы:

  • annotationClass - класс аннотации (не null). Если передан null, метод выбрасывает NullPointerException.

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

  • true - если указанная аннотация присутствует на элементе. Под "присутствует" понимается либо прямое наличие аннотации, либо «косвенное» наличие в случаях, когда это предусмотрено спецификацией (например, для repeatable-аннотаций контейнер учитывается).
  • false - если аннотация отсутствует или недоступна в рантайме (например, имеет RetentionPolicy.SOURCE или CLASS).

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

  • Для классов учитывается мета-аннотация @Inherited. Если аннотация типа помечена @Inherited, то наследники могут получить true, даже если аннотация стоит в суперклассе и не объявлена в подклассе. Для методов и полей @Inherited не применяется.
  • Аннотации с RetentionPolicy.RUNTIME доступны в рантайме. Аннотации с RetentionPolicy.SOURCE и CLASS недоступны и будут восприниматься как отсутствующие.
  • В Java 8 добавлена поддержка repeatable-аннотаций и контейнеров. Метод фактически равен проверке getAnnotation(annotationClass) != null и учитывает контейнеры для repeatable-аннотаций.
  • Метод не возвращает информацию о значениях аннотации, только факт присутствия. Для чтения значений используется getAnnotation или getAnnotationsByType.

Простые примеры

Классическая проверка наличия аннотации на классе:

import java.lang.annotation.*;

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

@A
class C {}

public class E {
    public static void main(String[] args) {
        System.out.println(C.class.isAnnotationPresent(A.class));
    }
}
true

Проверка аннотации на методe:

import java.lang.annotation.*;
import java.lang.reflect.Method;

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

class X {
    @MAnn
    public void f() {}
}

public class Demo {
    public static void main(String[] args) throws Exception {
        Method m = X.class.getMethod("f");
        System.out.println(m.isAnnotationPresent(MAnn.class));
    }
}
true

Аннотация с RetentionPolicy.CLASS или SOURCE недоступна в рантайме:

import java.lang.annotation.*;

@Retention(RetentionPolicy.SOURCE)
@interface SAnn {}

@SAnn
class Y {}

public class T {
    public static void main(String[] args) {
        System.out.println(Y.class.isAnnotationPresent(SAnn.class));
    }
}
false

Поведение наследования для классов (только если аннотация помечена @Inherited):

import java.lang.annotation.*;

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

@Inh
class Base {}

class Sub extends Base {}

public class TestInh {
    public static void main(String[] args) {
        System.out.println(Sub.class.isAnnotationPresent(Inh.class));
    }
}
true

Похожие API в Java

Внутри JDK есть несколько методов, связанных с получением аннотаций:

  • getAnnotation(Class<A> annotationClass) - возвращает сам объект аннотации или null. Предпочтительнее, когда требуется не только факт, но и значения полей аннотации.
  • getDeclaredAnnotation и getDeclaredAnnotations - возвращают только аннотации, явно объявленные на этом элементе (без учета наследования или контейнеров).
  • getAnnotationsByType - полезен для repeatable-аннотаций, возвращает массив всех экземпляров указанного типа, включая развёрнутые из контейнера.
  • Spring: AnnotatedElementUtils.hasAnnotation и AnnotationUtils.findAnnotation - выполняют поиск с учётом мета-аннотаций и композиции, удобны для фреймворковых сценариев.

Выбор между ними зависит от задачи: для булевой проверки достаточно isAnnotationPresent; для доступа к значениям - getAnnotation; для repeatable-аннотаций и контейнеров - getAnnotationsByType; для поиска мета-аннотаций и компонентов Spring - утилиты Spring.

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

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

C# (System.Reflection):

using System;

[AttributeUsage(AttributeTargets.Class)]
class A : Attribute {}

[A]
class C {}

class P { static void Main() { Console.WriteLine(Attribute.IsDefined(typeof(C), typeof(A))); } }
True

Отличие: есть удобный метод Attribute.IsDefined, поведение с наследованием контролируется AttributeUsage.

Python (декораторы и атрибуты):

def ann(cls):
    cls._ann = True
    return cls

@ann
class C: pass

print(hasattr(C, '_ann'))
True

Отличие: аннотации реализуются как присвоение атрибутов или декораторы, нет единого стандарта типа Java-Annotations.

JavaScript (reflect-metadata, декораторы):

import 'reflect-metadata';

function A(target) { Reflect.defineMetadata('A', true, target); }

@A
class C {}

console.log(Reflect.getMetadata('A', C));
true

Отличие: требует внешнюю библиотеку и экспериментальные декораторы; метаданные хранятся по ключам.

PHP (аннотации в докблоках, Doctrine Annotations):

/**
 * @A
 */
class C {}

// Doctrine читает докблок и парсит аннотацию
// Выход зависит от парсера
(зависит от парсера) true

Отличие: отсутствует встроенная система аннотаций, чаще используются докблоки и парсеры.

Go (теги полей):

package main

import (
    "fmt"
    "reflect"
)

type S struct { F string `json:"f"` }

func main(){
    t := reflect.TypeOf(S{})
    f, _ := t.FieldByName("F")
    fmt.Println(f.Tag.Get("json"))
}
f

Отличие: в Go аннотации представлены как строковые теги полей структур, без общей системы аннотаций на типы/методы.

Kotlin (reflection):

annotation class A

@A
class C

fun main(){
    println(C::class.annotations.any { it.annotationClass == A::class })
}
true

Отличие: Kotlin интегрируется с Java-аннотациями и имеет свой API kotlin.reflect для проверки наличия аннотаций.

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

1) Передача null в метод:

Object o = null;
// o.isAnnotationPresent(null) невозможен, но пример с прямым вызовом
Class<?> c = String.class;
System.out.println(c.isAnnotationPresent(null));
Exception in thread "main" java.lang.NullPointerException
    at java.base/java.lang.Class.isAnnotationPresent(Class.java:?)
    ...

2) Ожидание доступа к аннотациям с RETENTION отличным от RUNTIME:

import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@interface S {}

@S
class A {}

System.out.println(A.class.isAnnotationPresent(S.class));
false

3) Ожидание наследования аннотаций для методов и интерфейсов (мистификация):

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@interface MA {}

interface I { @MA void m(); }

class C implements I { public void m() {} }

System.out.println(C.class.getMethod("m").isAnnotationPresent(MA.class));
false

Комментарий: метод, реализующий интерфейс, не унаследует аннотацию интерфейсного метода автоматически.

4) Сравнение аннотаций из разных загрузчиков классов может дать неожиданный результат, поскольку классы аннотаций считаются разными, если их загрузили разные ClassLoader.

Изменения в поведении в последних выпусках Java

Ключевые эволюции API аннотаций:

  • Java 5 ввела систему аннотаций и интерфейс AnnotatedElement с методом isAnnotationPresent.
  • Java 8 добавила поддержку repeatable-аннотаций и методы getAnnotationsByType, а также учёт контейнера repeatable-аннотаций при поиске. Поведение isAnnotationPresent было согласовано с этими изменениями.
  • Дальнейшие версии Java улучшали возможности рефлексии и добавляли новые места применения аннотаций (например, аннотации типов), но поведение isAnnotationPresent осталось стабильным: оно по-прежнему сообщает о факте наличия аннотации в рантайме.

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

1) Repeatable-аннотации и контейнеры:

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

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Vals.class)
@interface Val { String value(); }

@Retention(RetentionPolicy.RUNTIME)
@interface Vals { Val[] value(); }

@Val("a")
@Val("b")
class D {}

public class R {
    public static void main(String[] args) {
        System.out.println(D.class.isAnnotationPresent(Val.class));
        java.util.Arrays.stream(D.class.getAnnotationsByType(Val.class)).forEach(v->System.out.println(v.value()));
    }
}
true
a
b

Пояснение: isAnnotationPresent(Val.class) возвращает true, а getAnnotationsByType разворачивает все экземпляры.

2) Проверка аннотаций параметров метода:

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

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

class K { void m(@P String s) {} }

public class ParamDemo {
    public static void main(String[] args) throws Exception {
        Method mm = K.class.getDeclaredMethod("m", String.class);
        Parameter p = mm.getParameters()[0];
        System.out.println(p.isAnnotationPresent(P.class));
    }
}
true

Пояснение: объект Parameter реализует AnnotatedElement, поэтому для параметров также применимы те же методы проверки.

3) Получение значений аннотации после положительной проверки:

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

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

@Info("ok")
class Z {}

public class Use {
    public static void main(String[] args) {
        if (Z.class.isAnnotationPresent(Info.class)) {
            Info i = Z.class.getAnnotation(Info.class);
            System.out.println(i.value());
        }
    }
}
ok

Пояснение: частая комбинация - сначала булева проверка, затем извлечение объекта аннотации для чтения полей.

4) Аннотации и динамические прокси:

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

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

interface IF { @IA void m(); }

class Run {
    public static void main(String[] args) {
        IF proxy = (IF) Proxy.newProxyInstance(IF.class.getClassLoader(), new Class[]{IF.class}, (p, m, a)->null);
        // Прокси-класс сам по себе не имеет аннотации метода интерфейса
        try {
            Method pm = proxy.getClass().getMethod("m");
            System.out.println(pm.isAnnotationPresent(IA.class));
            // Требуется обратиться к интерфейсу
            Method im = IF.class.getMethod("m");
            System.out.println(im.isAnnotationPresent(IA.class));
        } catch (NoSuchMethodException e) { }
    }
}
false
true

Пояснение: прокси-класс не наследует аннотации интерфейса автоматически, поэтому проверка должна выполняться на самом интерфейсе.

5) Совмещение с утилитами фреймворка для поиска мета-аннотаций:

Пример java
// Spring-style: AnnotatedElementUtils.hasAnnotation(element, MyAnn.class)
// Вернёт true, если аннотация присутствует косвенно через мета-аннотацию.
// Результат отличается от простого isAnnotationPresent в случаях мета-аннотаций.
(результат зависит от контекста и наличия Spring)

джава isAnnotationPresent function comments

En
IsAnnotationPresent Returns true if an annotation of the specified type is present