IsAnnotationPresent: примеры (JAVA)
isAnnotationPresent(Class extends Annotation> 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-аннотации и контейнеры:
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) Проверка аннотаций параметров метода:
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) Получение значений аннотации после положительной проверки:
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) Аннотации и динамические прокси:
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) Совмещение с утилитами фреймворка для поиска мета-аннотаций:
// Spring-style: AnnotatedElementUtils.hasAnnotation(element, MyAnn.class)
// Вернёт true, если аннотация присутствует косвенно через мета-аннотацию.
// Результат отличается от простого isAnnotationPresent в случаях мета-аннотаций.
(результат зависит от контекста и наличия Spring)