元数据卡
- 前置知识:第7章(面向对象基础)、第8章(继承与多态)
- 预计时间:25 分钟
- 完成标志:能读懂 @Override/@Deprecated/@SuppressWarnings;能定义自己的注解
你可曾在代码里见过 @Override ?就是方法上面那个小小的 @ 标记。看起来不起眼,但它的故事比你想象的深。
想象你在铁匠铺铸了一把剑,然后在剑柄上刻了一行字:"丙寅年仲春,老陈锻造"。这行字不影响剑的锋利度,也不改变剑的重量——但谁拿到这把剑,一看就知道出处和批次。
注解在代码里就是干这个的。它是刻在代码上的元数据。
标准注解:拿来就用的小标签
@Override——安全带,不是装饰
你在工坊里继承了一个 Animal 类,想改写它的 speak 方法。但如果你不小心打成了 speek 呢?
// 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 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 里指明替代方案:
/**
* @deprecated 从 v2.0 起不再推荐,请使用 {@link #searchByISBN(String)}
*/
@Deprecated
public Book searchBook(String keyword) { ... }@SuppressWarnings——按住编译器别嚷嚷
你用了原始类型的 List,你知道自己在做什么,但编译器还在一遍遍唠叨:"警告:unchecked operation"。你可以在方法上贴一个 @SuppressWarnings:
// 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 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 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 说活多久。
下一步
标签贴上之后谁来读?下一节你会学到如何用反射在运行时读取这些标签,把"刻在剑上的文字"变成真正起作用的行为。