499. Java 反射 - 获取类型上的注解
1. 背景介绍
- Java 8 引入了
TYPE_USE注解目标 ,意味着注解不仅可以标记在类、方法、字段上,还可以直接标记在类型使用的位置。 - 举例:继承、实现接口、方法参数、返回值、异常声明等地方都可以放置注注解。
- 为了支持这种能力,反射 API 提供了
AnnotatedType接口,用来访问类型上的注解。
2. 定义一个 @NonNull 注解
java
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface NonNull {}
这里用 TYPE_USE 表示:注解可以用在"类型使用"的地方,而不是仅仅在"类型定义"上。
3. 在类型使用上应用注解
java
public class Person {}
public class User
extends @NonNull Person
implements @NonNull Serializable {}
👉 这里:
User继承了@NonNull PersonUser实现了@NonNull Serializable
4. 使用反射获取类型注解
java
Class<?> c = User.class;
// 获取父类上的注解
AnnotatedType superClass = c.getAnnotatedSuperclass();
for (Annotation annotation : superClass.getAnnotations()) {
System.out.println("annotation on the super class = " + annotation);
}
// 获取接口上的注解
AnnotatedType[] interfaces = c.getAnnotatedInterfaces();
for (AnnotatedType annotatedInterface : interfaces) {
for (Annotation annotation : annotatedInterface.getAnnotations()) {
System.out.println("annotation on the implemented interface = " + annotation);
}
}
运行结果:
java
annotation on the super class = @org.devjava.NonNull()
annotation on the implemented interface = @org.devjava.NonNull()
💡 要点:
getAnnotatedSuperclass():查看父类上的类型注解。getAnnotatedInterfaces():查看接口上的类型注解。
5. 在异常声明上使用注解
有时我们希望给异常类型本身加注解,比如告诉调用者某个异常可以安全地转化为 RuntimeException。
定义注解
java
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReThrowAsRuntimeException {}
在构造器上使用
java
class EmptyStringException extends Exception {
public EmptyStringException(String message) {
super(message);
}
}
public class Message {
private final String name;
public Message(String name) throws @ReThrowAsRuntimeException EmptyStringException {
if (name == null || name.isEmpty()) {
throw new EmptyStringException("name is empty");
}
this.name = name;
}
}
6. 客户端代码示例
java
try {
Message message = new Message(""); // 会触发异常
} catch (EmptyStringException e) {
Constructor<?> constructor = Message.class.getConstructor(String.class);
AnnotatedType[] exceptionTypes = constructor.getAnnotatedExceptionTypes();
for (AnnotatedType exceptionType : exceptionTypes) {
if (exceptionType.isAnnotationPresent(ReThrowAsRuntimeException.class)) {
throw new RuntimeException(e); // 自动包装成运行时异常
}
}
}
运行结果:
java
Exception in thread "main" java.lang.RuntimeException: org.devjava.EmptyStringException: name is empty
Caused by: org.devjava.EmptyStringException: name is empty
7. 总结
- 普通注解 用在类、方法、字段;类型注解用在"类型使用"的地方。
- 使用反射时:
getAnnotatedSuperclass()→ 父类的类型注解getAnnotatedInterfaces()→ 接口的类型注解getAnnotatedExceptionTypes()→ 异常类型的注解
- 这种能力非常适合做 代码质量检查、契约验证 (比如
@NonNull)、以及 异常处理策略 (比如@ReThrowAsRuntimeException)。