注解让代码更智能
😫痛点引入:代码里一堆注释,运行时完全没用?想让编译器帮你检查@Override 是否写对?
注解就是 Java 的「元数据标签」——编译期和运行时都能用,让代码更智能!✨
今天从内置注解到自定义注解,一篇让你精通注解机制 + JUnit 单元测试!
一、注解是什么?🤔——代码的「元数据标签」🏷️
1.1 官方定义
注解(Annotation):也叫元数据,是 JDK 1.5 引入的特性,与类、接口、枚举同级别。
核心特点:
- 可以标注在包、类、字段、方法、局部变量、方法参数等元素上
- 对程序的编译和运行起到一定作用(注释只是给人看的,注解会影响程序)
1.2 生活类比
注释:就像便利贴 📝,写给程序员看的,程序运行没影响 注解:就像商品标签 🏷️,编译器/运行时读取,会影响程序行为 @Override → 编译器检查:这个方法真的是重写吗? @Test → JUnit 看到后会自动运行这个方法1.3 注解的本质
// 注解本质上就是一个接口,默认继承 java.lang.annotation.Annotationpublic@interfaceMyAnnotationextendsAnnotation{// 属性列表}二、常见内置注解速查 📋
| 注解 | 作用位置 | 功能 | 保留策略 |
|---|---|---|---|
| @author | 类/接口 | 标识作者名 | SOURCE(源码级别) |
| @version | 类/接口 | 标识版本号 | SOURCE |
| @param | 方法参数 | 方法参数说明 | SOURCE |
| @return | 方法 | 返回值说明 | SOURCE |
| @Override✅ | 方法 | 编译器检查是否真重写 | SOURCE |
| @Deprecated⚠️ | 类/方法/字段 | 标识已过时,编译器警告 | SOURCE |
| @SuppressWarnings | 类/方法 | 压制编译器警告 | SOURCE |
2.1 @Override 注解解析(面试常问 💼)
classFu{publicvoidshow(){System.out.println("父类方法");}}classZiextendsFu{// @Override 注解:编译器会检查!// 1. 到父类中找是否有相同方法签名(名称+参数)的方法// 2. 如果有 → 编译通过 ✅// 3. 如果没有 → 编译报错 ❌(防止你写错方法名)@Overridepublicvoidshow(){System.out.println("子类重写方法");}// ❌ 编译错误!父类中没有 show2() 方法// @Override// public void show2() {}}💡 为什么需要 @Override?
不用注解: public void showw() { ... } // 写错了!但编译器不报错,运行时才发现问题 😱 用了注解: @Override public void showw() { ... } // 编译直接报错!提前发现问题 ✅三、元注解——注解的「注解」🏷️
元注解:用来描述其他注解的注解(4个元注解)。
3.1 四大元注解速查表
| 元注解 | 功能 | 常用取值 |
|---|---|---|
| @Target | 描述注解能用在哪 | ElementType[] 数组 |
| @Retention | 描述注解的生命周期 | RetentionPolicy 枚举 |
| @Documented | 描述注解是否抽取到 JavaDoc | 无属性 |
| @Inherited | 描述子类是否继承父类注解 | 无属性 |
3.2 @Target —— 指定使用位置
作用:限制注解可以用在哪些地方。
| ElementType 取值 | 可用位置 |
|---|---|
| TYPE | 类、接口、枚举 |
| FIELD | 字段(成员变量) |
| METHOD | 方法 ✅ 最常用 |
| PARAMETER | 方法参数 |
| CONSTRUCTOR | 构造方法 |
| LOCAL_VARIABLE | 局部变量 |
| ANNOTATION_TYPE | 元注解(注解的注解) |
importjava.lang.annotation.*;// ✅ 这个注解只能用在方法上!@Target(ElementType.METHOD)@interfaceMyMethodAnno{Stringvalue();}classDemo01{@MyMethodAnno("测试")// ✅ 用在方法上,正确publicvoidtest(){}// ❌ 编译错误!不能用在字段上// @MyMethodAnno("测试")// private String name;}3.3 @Retention —— 指定生命周期 ⭐ 最重要!
作用:决定注解在哪个阶段有效。
| RetentionPolicy 取值 | 生命周期 | 说明 |
|---|---|---|
| SOURCE | 源码级别 | 编译时丢弃,@Override 就是这个 ❌ |
| CLASS | 字节码级别 | 保留到 .class 文件,但 JVM 不读取(默认) |
| RUNTIME⭐ | 运行时 | 保留到 .class 且 JVM 会读取,反射可用 ✅ |
importjava.lang.annotation.*;// ✅ 这个注解在运行时有效,可以通过反射读取!@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@interfaceMyRuntimeAnno{Stringvalue();}classDemo02{@MyRuntimeAnno("可运行时读取")publicvoidtest(){}}💡 如何选 RetentionPolicy?
| 场景 | 选择 |
|---|---|
| 编译期检查(@Override) | SOURCE |
| 字节码增强工具(AspectJ) | CLASS |
| 运行时反射读取(最常用)✅ | RUNTIME |
四、自定义注解 —— 从零手写 🏗️
4.1 定义格式
// 元注解(描述这个注解的特性)@Target(ElementType.TYPE)// 用在类上@Retention(RetentionPolicy.RUNTIME)// 运行时有效public@interface注解名{// 属性列表(很像方法定义)数据类型 属性名()[default默认值];}4.2 属性类型(支持的数据类型)
✅8 大基本数据类型(int, byte, short, long, float, double, char, boolean)
✅String类型
✅Class类型
✅枚举类型
✅注解类型
✅以上类型的数组(如String[] value())
4.3 注意事项
| 规则 | 说明 |
|---|---|
| 属性无参数 | 像方法定义,但没参数列表String name() |
| 默认值可选 | String name() default "默认" |
| 特殊属性名 value | 如果只有一个属性且叫 value,使用时可省略属性名 |
| 数组属性 | 使用时arr = {"a", "b"},单个元素可省略大括号 |
4.4 代码示例
importjava.lang.annotation.*;@Target({ElementType.TYPE,ElementType.METHOD})// 可用在类和方法上@Retention(RetentionPolicy.RUNTIME)public@interfacePersonAnno{// 属性(只有一个 value 时,使用时可省略属性名)Stringvalue();// 无默认值,使用时必须赋值intage()default18;// 有默认值,可省略Stringsex()default"男";// 有默认值,可省略}// 使用注解@PersonAnno(value="书源",age=20,sex="男")// 完整写法// 或者:@PersonAnno("书源") // 只有一个 value,可省略属性名!classStudent{@PersonAnno("学习方法")// 用在方法上(因为 @Target 包含了 METHOD)publicvoidstudy(){}}五、注解的解析 —— 反射读取 🔍
核心原理:注解本身只是标注,真正的功能是通过反射解析实现的!
5.1 解析步骤
1. 获取注解标注位置的对象(Class / Method / Field) 2. 调用 getAnnotation(Class) 获取注解对象 3. 调用注解的「属性方法」获取配置的属性值5.2 核心 API 速查
| 方法 | 功能 | 返回值 |
|---|---|---|
isAnnotationPresent(Class) | 判断是否有指定注解 | boolean |
getAnnotation(Class) | 获取指定注解对象 | 注解对象 |
getAnnotations() | 获取所有注解 | Annotation[] |
5.3 代码示例:解析类上的注解
importjava.lang.annotation.*;importjava.lang.reflect.*;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@interfaceClassAnno{Stringname();intpriority()default1;}@ClassAnno(name="测试类",priority=5)classTestClass{publicvoidshow(){}}classDemo03{publicstaticvoidmain(String[]args)throwsException{// 1. 获取 Class 对象Class<?>c=TestClass.class;// 2. 判断是否有指定注解if(c.isAnnotationPresent(ClassAnno.class)){// 3. 获取注解对象ClassAnnoanno=c.getAnnotation(ClassAnno.class);// 4. 调用注解的属性方法,获取值Stringname=anno.name();intpriority=anno.priority();System.out.println("name = "+name);// 测试类System.out.println("priority = "+priority);// 5}}}5.4 代码示例:解析方法上的注解(实战 💪)
importjava.lang.annotation.*;importjava.lang.reflect.*;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@interfaceInitMethod{Stringdesc()default"初始化方法";}classUserService{@InitMethod(desc="加载用户数据")publicvoidinit(){System.out.println("用户数据加载完成 ✅");}@InitMethod(desc="校验用户权限")publicvoidcheck(){System.out.println("权限校验完成 ✅");}publicvoidother(){System.out.println("普通方法,无注解");}}classDemo04{publicstaticvoidmain(String[]args)throwsException{// 扫描类中所有带 @InitMethod 注解的方法,自动执行!Class<?>c=UserService.class;Objectobj=c.newInstance();Method[]methods=c.getDeclaredMethods();for(Methodm:methods){// 判断方法上是否有 @InitMethod 注解if(m.isAnnotationPresent(InitMethod.class)){InitMethodanno=m.getAnnotation(InitMethod.class);System.out.println("执行:"+anno.desc());m.invoke(obj);// 反射调用方法}}}}运行结果:
执行:加载用户数据 用户数据加载完成 ✅ 执行:校验用户权限 权限校验完成 ✅💡 这就是 Spring 注解的底层原理!@PostConstruct、@PreDestroy等注解,就是通过类似机制实现的!
六、JUnit 单元测试 —— 一个类多个「main 方法」🏃️
6.1 什么是 JUnit?
JUnit:由 Kent Beck 和 Erich Gamma 建立的回归测试框架,是 xUnit 家族中最成功的一个。
痛点解决:
传统方式: public static void main(String[] args) { test1(); // 只能有一个 main,想测多个方法得注释掉 } JUnit 方式: @Test public void test1() {} // 每个方法都能单独运行!✅ @Test public void test2() {} // 一个类可以有多个测试方法6.2 @Before 和 @After(前置/后置处理)
| 注解 | 执行时机 | 作用 |
|---|---|---|
| @Before | 每个 @Test 方法前执行 | 初始化资源(如数据库连接) |
| @After | 每个 @Test 方法后执行 | 释放资源 |
6.3 使用要求和代码示例
⚠️ 注意事项:
- 方法必须是
public - 无返回值(
void) - 无参数列表
- 类名不要叫
Test(避免冲突)
importorg.junit.*;/** * JUnit 4 单元测试示例 * 要求:public void 无参数 */publicclassDemo05{// @Before:每个 @Test 方法执行前都会先执行!@Beforepublicvoidbefore(){System.out.println("🔧 初始化资源(Before)");}// @After:每个 @Test 方法执行后都会执行!@Afterpublicvoidafter(){System.out.println("🧹 释放资源(After)");}@Testpublicvoidinsert(){System.out.println("📝 测试 insert 方法");}@Testpublicvoidupdate(){System.out.println("📝 测试 update 方法");}}运行结果:
🔧 初始化资源(Before) 📝 测试 insert 方法 🧹 释放资源(After) 🔧 初始化资源(Before) 📝 测试 update 方法 🧹 释放资源(After)6.4 ⚠️ 导入 JUnit 包
<!-- Maven 依赖 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency>或手动导入junit-4.xx.jar到项目。
七、注解 vs 注释 —— 面试速查 📊
| 对比项 | 注释 | 注解 |
|---|---|---|
| 给谁看 | 程序员 👀 | 编译器 / JVM 🤖 |
| 影响运行 | ❌ 不影响 | ✅ 编译期/运行期起作用 |
| 保留位置 | 源码中,编译后消失 | SOURCE/CLASS/RUNTIME 可选 |
| 能否有逻辑 | ❌ 纯文本 | ✅ 通过反射实现功能 |
| 典型应用 | 说明代码功能 | @Override/@Test/自定义注解 |
本篇总结 📝
- 注解概念🤔:JDK 1.5 引入,与类/接口/枚举同级别,影响编译和运行
- 内置注解📋:@author/@version/@Override(编译检查)/@Deprecated
- 元注解🏷️:@Target(使用位置)/ @Retention(生命周期 ⭐最重要)/ @Documented/ @Inherited
- 自定义注解🏗️:@interface 定义,属性支持8种基本类型/String/Class/枚举/注解/数组
- 注解解析🔍:反射 + getAnnotation() 获取注解,调用属性方法获取值
- 实战应用💪:扫描带注解的方法自动执行(类似 Spring 原理)
- JUnit 单元测试🏃️:@Test 让一个类有多个测试方法,@Before/@After 前置后置处理
🚀 下一篇预告:《四十二、网络编程基础——Socket 通信入门》—— 搞定 IP/端口/协议,手写客户端-服务端通信,理解 HTTP 背后的原理!
作者:书源丶
发布平台:CSDN