跳到内容

元数据卡

  • 前置知识:第7章(面向对象基础)、第8章(继承与多态)
  • 预计时间:25 分钟
  • 完成标志:能读懂 @Override/@Deprecated/@SuppressWarnings;能定义自己的注解

你可曾在代码里见过 @Override ?就是方法上面那个小小的 @ 标记。看起来不起眼,但它的故事比你想象的深。

想象你在铁匠铺铸了一把剑,然后在剑柄上刻了一行字:"丙寅年仲春,老陈锻造"。这行字不影响剑的锋利度,也不改变剑的重量——但谁拿到这把剑,一看就知道出处和批次。

注解在代码里就是干这个的。它是刻在代码上的元数据。

标准注解:拿来就用的小标签

@Override——安全带,不是装饰

你在工坊里继承了一个 Animal 类,想改写它的 speak 方法。但如果你不小心打成了 speek 呢?

java
// Java 8+
// 运行方式:javac Dog.java
class Animal {
    public void speak() {
        System.out.println("Animal speaks");
    }
}

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("Woof!");
    }

    @Override
    public void bark() {  // 编译报错
        System.out.println("Bark!");
    }
}

预期输出:javac 报错 method does not override or implement a method from a supertype

不加 @Override 时,bark() 只是 Dog 的新方法,编译器不关心。加了 @Override,编译器帮你确认:"你说要覆写,但父类没这个方法啊"。它救过无数人因为拼写错误引入的隐形 bug。

@Deprecated——"这东西老了,别用了"

旧磨具还能用,但效率低。你不该直接拆掉它——也许有人还在用。怎么办?贴个标签提醒他们。

java
// Java 5+
// 运行方式:javac OldTool.java 观察警告
public class OldTool {
    @Deprecated
    public void oldMethod() {
        System.out.println("这个方法过时了");
    }

    public void newMethod() {
        System.out.println("新方法,推荐使用");
    }
}

预期输出:编译时终端出现 deprecation warning,IDE 里 oldMethod() 被画上中划线。

更规范的写法是在 Javadoc 里指明替代方案:

java
/**
 * @deprecated 从 v2.0 起不再推荐,请使用 {@link #searchByISBN(String)}
 */
@Deprecated
public Book searchBook(String keyword) { ... }

@SuppressWarnings——按住编译器别嚷嚷

你用了原始类型的 List,你知道自己在做什么,但编译器还在一遍遍唠叨:"警告:unchecked operation"。你可以在方法上贴一个 @SuppressWarnings:

java
// Java 5+
// 运行方式:javac SuppressDemo.java
import java.util.ArrayList;
import java.util.List;

public class SuppressDemo {
    @SuppressWarnings("unchecked")
    public void processRawList() {
        List rawList = new ArrayList();
        rawList.add("hello");
        List<String> safeList = (List<String>) rawList;
    }
}

预期输出:编译通过,无 warning 输出(本来会出现的 unchecked 警告被压制了)。

常用压制参数:"unchecked"(未检查类型转换)、"deprecation"(使用了过时元素)、"all"(所有警告——慎用)。

使用原则:@SuppressWarnings 相当于对编译器说"我知道我在做什么"。这句话的前提是你真的知道。

自定义注解:造你自己的标签

标准注解只有几个,不够用怎么办?自己造。

先看怎么定义:

java
// Java 5+
// 文件名:Loggable.java
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 Loggable {
    String level() default "INFO";
    boolean includeParams() default true;
}

语法拆解:

  • @interface——声明"这是一个注解类型",不是接口
  • @Target——元注解(注解的注解),告诉编译器这个注解能贴在哪
  • @Retention——元注解,告诉编译器这个注解活多久
  • level()——注解的属性,看起来像方法,实际上是配置项

元注解速查:

元注解作用
@Target贴在哪:METHOD/FIELD/TYPE(类/接口)/PARAMETER/CONSTRUCTOR
@Retention活多久:SOURCE(编译丢弃)/CLASS(字节码保留)/RUNTIME(反射可读)
@Documented是否出现在 Javadoc 中
@Inherited子类是否继承父类的该注解

定义好之后就能这么用:

java
// Java 5+
public class OrderService {
    @Loggable(level = "DEBUG")
    public void placeOrder(String userId, String itemId) {
        System.out.println("下单中...");
    }

    @Loggable  // 全部默认值
    public void queryOrder(String orderId) {
        System.out.println("查询订单中...");
    }
}

注解属性的类型限制:基本类型、String、Class、枚举、注解类型、以上类型的数组。

常见陷阱

"我定义的注解运行时读不到"——99% 忘了加 @Retention(RetentionPolicy.RUNTIME)。默认是 CLASS,编译时在字节码里,JVM 加载后不保留,反射拿不到。

旅人笔记

注解 = 代码上的标签。不改变行为,但告诉别人"我是谁"。@Override 是安全网,@Deprecated 是善意提醒,@SuppressWarnings 是对编译器说"我知道"。自定义注解 = @interface + @Target + @Retention。元注解 = 注解的注解——@Target 说贴哪,@Retention 说活多久。

下一步

标签贴上之后谁来读?下一节你会学到如何用反射在运行时读取这些标签,把"刻在剑上的文字"变成真正起作用的行为。

前往第13章续篇:反射——程序照镜子

Built with VitePress | Software Systems Atlas