☕ 第九章 反射机制和注解
📝 一句话概括

注释是给人看的,注解是给程序看的——一张贴在代码上的标签。这节学会认标签(内置三大件)、造标签(自定义注解 + 元注解),为 9.5 反射读注解备好货。

1. 引入:注释 vs 注解,一字之差

一句话记住:注解 = 标签,代码是商品。至于标签管不管用,得看有没有"人"来读它。

@Deprecated                        // ← 标签:这玩意儿过时了
public class Student {
    @Override                      // ← 标签:我重写了父类方法,编译器帮我盯着
    public String toString() { ... }
}

2. 注解的三大读者

Java 内置三大注解速查

注解 贴在哪 作用 旧账
@Override 方法 声明"我重写了父类方法",拼错方法名直接编译报错 4.1 继承的重写
@Deprecated 任意 标记过时,调用处画删除线给警告 6.6 的过时 Date 构造
@SuppressWarnings("all") 任意 压制警告 6.5 的 BigDecimal 类型警告
📝 @Override 为什么值得贴?

不贴也能重写——但贴上后,你把 toString 敲成 tostring,编译器当场报错;不贴则悄悄变成一个新方法,重写失败你毫不知情。标签把"我以为"变成"编译器确认"。

3. 自定义注解:造自己的标签

public @interface MyAnno {           // @interface —— 注意不是 interface
    String name();                   // "属性":长得像方法,其实是属性
    int age() default 18;            // 带 default 可以不赋值
}

使用与赋值规则:

@MyAnno(name = "波仔")               // age 有默认值,可不写
@MyAnno(name = "波仔", age = 25)     // 全赋值
@MyAnno("波仔")                      // 特例:唯一属性叫 value 时,可省略属性名

四条规矩:

  1. 关键字是 @interface,比普通接口多个 @。
  2. "属性"长得像抽象方法(String name();),但语义是属性声明,用 name = "波仔" 赋值。
  3. 不写 default 的属性,贴注解时必须赋值,否则编译报错。
  4. 属性类型有限制:八种基本类型、String、Class、枚举、注解、以及它们的数组——不能随便放个 Student 类型。

4. 元注解:管标签的标签

自定义注解贴出去之前,得先回答两个问题——能贴哪?活多久?这由两个元注解决定(元注解 = 贴在注解定义上的注解):

@Target(ElementType.METHOD)                              // 只能贴在方法上
@Retention(RetentionPolicy.RUNTIME)                      // 运行时还在,反射能读
public @interface MyTest {
}
⚠️ 本节最值钱的一句话

想让自己的注解被反射解析,@Retention(RetentionPolicy.RUNTIME) 必须写。忘了写就是默认 CLASS——标签贴得再认真,运行时早没了,反射一无所获。框架的注解清一色 RUNTIME。

5. 收网旧账

6. 动手:Demo04 造一张自己的标签

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)                 // 只能贴方法上
@Retention(RetentionPolicy.RUNTIME)         // 运行时还在 → 反射可读
public @interface MyTest {
    String author() default "波仔";
}
public class Demo04 {
    @MyTest
    public void run() { System.out.println("贴了标签的方法"); }

    public void walk() { System.out.println("没贴标签的方法"); }

    public static void main(String[] args) throws Exception {
        Class<?> c = Class.forName("Demo04");
        for (Method m : c.getDeclaredMethods()) {                 // 9.3 的拆方法
            if (m.isAnnotationPresent(MyTest.class)) {            // 问:贴标签了吗?
                System.out.println("执行 → " + m.getName());
                m.invoke(c.getDeclaredConstructor().newInstance()); // 9.3 的造对象+调方法
            }
        }
    }
}

运行只执行 run()——这二十几行就是 JUnit 的雏形:框架扫描贴了 @Test 的方法并执行,一模一样的套路。

三个 30 秒实验:

  1. 把 @Retention 那行注释掉(默认 CLASS)再跑——什么都不输出:标签运行时没了,反射成了睁眼瞎。亲手体验"最值钱的一句话"。
  2. 把 @Target(ElementType.METHOD) 改成 @Target({ElementType.METHOD, ElementType.TYPE})——现在类上也能贴了,试试在 Demo04 类上贴 @MyTest。
  3. 给 run() 贴 @MyTest(author = "墨池"),在循环里用 m.getAnnotation(MyTest.class).author() 打印出来——标签是带信息的,不只是"有/没有"。

7. 常见误区

⚠️ 五个坑
  1. ❌ 把注解属性当方法调用 —— String name(); 是属性声明不是方法,赋值用 name = "波仔",没有 ()。
  2. ❌ 没 default 的属性不赋值 —— int age(); 不带 default,贴的时候必须写 age = 25,少一个都编译不过。
  3. ❌ 随便一个属性都想省名字 —— 只有"唯一属性恰好叫 value"才能写 @MyAnno("x");两个属性时省名直接报错。
  4. ❌ 忘写 @Retention(RUNTIME) 还纳闷反射读不到 —— 默认 CLASS 档,运行时标签已消失。读不到注解,先查保质期。
  5. ❌ 贴了注解就等它自己生效 —— 注解是死标签,没"人"读它就是废纸。生效靠三选一:编译器检查 / 反射解析 / 工具生成。所谓"框架黑魔法",就是框架在替你读标签。

8. 可折叠自测

❓ Q1:注释和注解的本质区别?

注释给人看,编译后消失,程序不知道它存在;注解给程序看,是代码的元信息,能被编译器检查、被反射在运行时读到。

❓ Q2:`@interface` 里写 `String name();`——这是定义方法还是定义属性?赋值怎么写?

语法像抽象方法,语义是属性。赋值在贴注解时用 @MyAnno(name = "波仔"),带 default 的可省略。

❓ Q3:什么时候能写 `@MyAnno("x")` 而不用写属性名?

注解只有一个属性、且属性名恰好叫 value 时,可以省略属性名。

❓ Q4:`@Retention` 三档各自活到哪?反射哪档能读到?

SOURCE 活在源码(编译即扔)、CLASS 活到字节码(运行时丢,默认值)、RUNTIME 运行时还在。反射只能读 RUNTIME 档。

❓ Q5(毕业题):不看笔记,写出"迷你 JUnit"两件套:①定义 `@MyTest`(只能贴方法、RUNTIME 档);②反射扫描某类,把贴了 `@MyTest` 的方法全部执行,没贴的跳过。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest { }

for (Method m : Class.forName("MyTestDemo").getDeclaredMethods()) {
    if (m.isAnnotationPresent(MyTest.class)) {
        m.invoke(Class.forName("MyTestDemo").getDeclaredConstructor().newInstance());
    }
}

写出来,你就亲手造出了测试框架的心脏。

9. 下一步学什么

相关笔记