注解 (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.RUNTIMEJVM 加载时保留,可用反射读取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
  启用: false

7.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 总结 —— 学习路线

  1. 会用@Override@Deprecated@SuppressWarnings
  2. 能读 — 看 Spring/MyBatis 的注解,理解其含义
  3. 会写 — 自定义 @interface + 元注解(@Retention + @Target 最重要)
  4. 会读 — 反射 + getAnnotation() / isAnnotationPresent()
  5. 会玩 — 注解 + 反射组合,写出自己的小框架

注解本身 ” 什么都不做 “,真正干活的是读取注解的代码(编译器、框架、你的反射代码)。