注解 (Annotation)
1 什么是注解?
注解 是 Java 5 引入的一种元数据机制,它不直接影响程序逻辑,而是为代码提供附加信息,供编译器、工具或运行时读取。
| 对比 | 注解 (Annotation) | 注释 (Comment) |
|---|---|---|
| 作用 | 给编译器/JVM/框架看 | 给人看 |
| 能否影响运行 | 可以(通过反射) | 完全不能 |
| 语法 | @注解名 | // 或 /* */ |
2 内置注解(JDK 自带的 3 个基础注解)
2.1 @Override — 方法重写标记
class Parent {
void speak() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override // 告诉编译器:这是重写父类方法
void speak() {
System.out.println("Child");
}
}✅ 如果方法签名写错(如
speek()),编译器会报错,提前发现 bug。
2.2 @Deprecated — 标记已过时
class OldClass {
@Deprecated
void oldMethod() {
System.out.println("这个方法过时了,别用了");
}
}
// 使用时编译器会给出警告(但依然能运行)
// oldClass.oldMethod(); // 编译时出现横线删除线2.3 @SuppressWarnings — 压制编译器警告
@SuppressWarnings("unchecked") // 压制"未检查类型转换"的警告
public void useRawType() {
List list = new ArrayList(); // raw type
list.add("hello"); // 不会有警告
}3 元注解(注解的注解)
元注解用来定义注解的行为,是注解的 ” 底层规则 “。
| 元注解 | 作用说明 |
|---|---|
@Retention | 注解保留到哪个阶段(源码 / 字节码 / 运行时) |
@Target | 注解可以用在什么地方(方法、类、字段……) |
@Documented | 是否出现在 javadoc 文档中 |
@Inherited | 子类是否自动继承该注解 |
@Repeatable | 同一个位置能否多次使用同一个注解 |
4 自定义注解(手写一个注解)
4.1 基本语法 —— 标记注解(不含元素)
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
}
@Loggable表示 ” 这个方法需要被日志记录 ”。
使用:
public class Service {
@Loggable
public void doWork() {
System.out.println("Working...");
}
}4.2 带元素的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
String unit() default "ms"; // 元素可以有默认值
boolean enabled() default true;
}使用:
public class Compute {
@Timer(unit = "ns", enabled = true)
public void fastCalc() { }
@Timer // 全部走默认值:unit="ms", enabled=true
public void slowCalc() { }
}如果元素没有默认值,使用时必须传值。
5 @Retention 详解 —— 保留策略
| 策略 | 说明 | 典型场景 |
|---|---|---|
RetentionPolicy.SOURCE | 只在源码中存在,编译后丢弃 | @Override |
RetentionPolicy.CLASS | 保留到 .class 文件,但 JVM 加载时不读 | 字节码增强工具 |
RetentionPolicy.RUNTIME | JVM 加载时保留,可用反射读取 | Spring、JUnit |
@Retention(RetentionPolicy.RUNTIME) // 运行期可读,反射才行
public @interface MyAnnotation {
}6 @Target 详解 —— 注解可以放哪里
| 取值 | 目标 |
|---|---|
ElementType.TYPE | 类、接口、枚举 |
ElementType.METHOD | 方法 |
ElementType.FIELD | 字段 |
ElementType.PARAMETER | 方法参数 |
ElementType.CONSTRUCTOR | 构造方法 |
ElementType.ANNOTATION_TYPE | 注解类型(即元注解的用法) |
ElementType.LOCAL_VARIABLE | 局部变量 |
@Target({ElementType.TYPE, ElementType.METHOD}) // 写在类和方法上都可以
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String name();
String date();
}7 运行时读取注解(反射)
这是注解最强大的用法 —— 通过反射在运行时获取注解信息并做出反应。
import java.lang.reflect.Method;
public class AnnotationProcessor {
public static void main(String[] args) throws Exception {
Method[] methods = MyService.class.getDeclaredMethods();
for (Method method : methods) {
// 判断方法是否有 @Timer 注解
if (method.isAnnotationPresent(Timer.class)) {
Timer timer = method.getAnnotation(Timer.class);
System.out.println("方法: " + method.getName());
System.out.println(" 单位: " + timer.unit());
System.out.println(" 启用: " + timer.enabled());
}
}
}
}
class MyService {
@Timer(unit = "ns")
public void methodA() { }
@Timer(enabled = false)
public void methodB() { }
public void methodC() { } // 没有注解
}输出:
方法: methodA
单位: ns
启用: true
方法: methodB
单位: ms
启用: false7.1 实战小例子 —— 用注解做 ” 简单测试框架 ”
// 1. 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
// 2. 使用注解
class MyTests {
@Test
public void test1() {
System.out.println("test1 通过");
}
@Test
public void test2() {
throw new RuntimeException("test2 挂了");
}
public void notATest() {
System.out.println("这不是测试");
}
}
// 3. 反射 + 处理
class TestRunner {
public static void main(String[] args) throws Exception {
MyTests obj = new MyTests();
int passed = 0, failed = 0;
for (Method m : MyTests.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(Test.class)) {
try {
m.invoke(obj);
passed++;
} catch (Exception e) {
System.out.println(m.getName() + " 失败: " + e.getCause());
failed++;
}
}
}
System.out.println("通过: " + passed + ",失败: " + failed);
}
}输出:
test1 通过
test2 失败: java.lang.RuntimeException: test2 挂了
通过: 1,失败: 1这就是 JUnit 等测试框架的核心原理。
8 @Repeatable —— 重复使用同一注解
8.1 旧版写法(Java 8 之前):只能用容器数组
public @interface Tasks {
Task[] value();
}
@Tasks({@Task("A"), @Task("B")})
public class Work { }8.2 新版写法(Java 8+)
// 1. 容器注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Tasks {
Task[] value();
}
// 2. 可重复注解,指明容器
@Repeatable(Tasks.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Task {
String value();
}
// 3. 使用 —— 可以写多个 @Task
@Task("需求分析")
@Task("编码")
@Task("测试")
public class Project {
}
// 4. 读取
class ReadRepeatable {
public static void main(String[] args) {
Task[] tasks = Project.class.getAnnotationsByType(Task.class);
for (Task t : tasks) {
System.out.println(t.value());
}
}
}输出:
需求分析
编码
测试9 @Inherited —— 子类继承注解
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
@MyAnnotation("父类注解")
class Parent { }
class Child extends Parent { } // 子类自动继承 @MyAnnotation
class TestInherited {
public static void main(String[] args) {
MyAnnotation a = Child.class.getAnnotation(MyAnnotation.class);
System.out.println(a.value()); // 输出: 父类注解
}
}⚠️ 注意:
@Inherited只对类上的注解生效,对方法、字段的注解无效。
10 注解 vs 接口 —— 一句话区分
- 接口:定义 ” 我能做什么 “(行为契约)
- 注解:定义 ” 我是什么 ”、” 我有什么属性 “(元数据标记)
| 维度 | 接口 | 注解 |
|---|---|---|
| 关键字 | interface | @interface |
| 实例化 | 由类实现 | 不可实例化,由反射读取 |
| 元素 | 抽象方法 + 常量 | 元素方法(类似无参方法) |
| 多继承 | 类可实现多个接口 | 一个位置可写多个注解 |
11 总结 —— 学习路线
- 会用 —
@Override、@Deprecated、@SuppressWarnings - 能读 — 看 Spring/MyBatis 的注解,理解其含义
- 会写 — 自定义
@interface+ 元注解(@Retention+@Target最重要) - 会读 — 反射 +
getAnnotation()/isAnnotationPresent() - 会玩 — 注解 + 反射组合,写出自己的小框架
注解本身 ” 什么都不做 “,真正干活的是读取注解的代码(编译器、框架、你的反射代码)。