大家好!如果你在 Java 开发中见过@Override@Controller这类带@的标记,却只知道 “贴上去能用”,说不出背后的逻辑 —— 那这篇入门博客正好适合你。今天我们从 “是什么”“怎么用” 两个角度,把注解的基础打牢,最后还会动手写一个能实际用的自定义注解,看完就能上手。

一、先搞懂:注解不是 “高级注释”,而是 “程序的标签”

第一次接触注解时,我总把它和注释搞混 —— 毕竟都带 “注” 字,而且看起来都是 “写在代码旁边的东西”。但后来踩了坑才明白,二者本质完全不同:

对比维度 注解(Annotation) 注释(Comment)
作用对象 程序 / 框架 / 编译器
处理方式 程序会解析并执行对应逻辑 编译器直接忽略
语法要求 严格遵循@注解名(属性)格式 随意写,// 或 /* */ 包裹就行

举个直观的例子:@Override注解贴在方法上,编译器会检查 “这个方法是不是真的重写了父类方法”—— 如果父类没有这个方法,直接爆红;而// 这是重写方法的注释,编译器只会当 “废话” 忽略。

所以,注解的核心是给代码贴 “机器能读懂的标签”,这些标签能告诉工具:“这段代码有特殊含义,你要这么处理它”。比如 Spring 看到@Controller,就知道 “这是个控制器,要把它注册成 Bean”;JUnit 看到@Test,就知道 “这是测试方法,要自动执行”。

二、Java 自带的 3 个基础注解:每天都在用,今天彻底懂

Java 从 1.5 就引入了注解,自带了 3 个最常用的基础注解,都在java.lang包下。平时写代码天天见,但很多人只知其然不知其所以然,我们逐个拆透。

1. @Override:重写方法的 “防错卫士”

作用:声明当前方法是 “重写父类 / 实现接口的方法”,核心价值是避免拼写错误

你可能会问:“我不写这个注解,重写方法不也能跑吗?”—— 确实能跑,但容易踩坑。比如父类有个getUserName(),我想重写时不小心写成getUserNam()(少个 e):

  • 没加@Override:编译器会把它当成 “新方法”,不报错,但运行时调用的还是父类方法,排查半天找不到问题;
  • 加了@Override:编译器会立刻爆红,提示 “父类没有这个方法”,直接把错误扼杀在编译阶段。

正确用法:只要重写方法,就贴在方法上,不用加任何属性:

class Parent {
    public String getUserName() {
        return "parent";
    }
}

class Child extends Parent {
    // 正确:贴@Override,编译器校验重写逻辑
    @Override
    public String getUserName() {
        return "child";
    }
    
    // 错误:父类没有getUserNam(),加@Override会爆红
    // @Override
    // public String getUserNam() { return "error"; }
}

2. @Deprecated:标记 “过时方法” 的 “警示牌”

作用:告诉其他开发者 “这个方法 / 类 / 字段已经过时,别用了,后续版本会删”,避免大家用淘汰的 API 写代码。

比如 Java 8 里Date类的toLocaleString()方法,就被标记为过时 —— 如果你调用它,IDE 会弹出黄色警告,提示 “用DateTimeFormatter替代”。

正确用法:贴在要标记的元素上,还能加两个实用属性:

public class Test {
    public static void main(String[] args) {
        // 临时屏蔽“调用过时方法”的警告
        @SuppressWarnings("deprecation")
        OldTool tool = new OldTool();
        tool.oldMethod(); // 警告消失
        
        // 屏蔽“变量未使用”的警告
        @SuppressWarnings("unused")
        String temp = "临时变量,暂时不用";
    }
}

三、注解的 “规则制定者”:元注解

前面的@Override@Deprecated是 “普通注解”,而元注解是 “给注解贴的注解”—— 简单说,元注解用来定义 “一个注解能贴在哪”“能活多久” 这些规则。

Java 自带 5 个元注解,都在java.lang.annotation包下,掌握它们是写自定义注解的前提。我们用一个 “自定义注解模板” 来理解:

1. @Target:规定 “注解能贴在哪”

@Target的参数是ElementType枚举,用来限制注解的 “作用范围”,比如有的注解只能贴方法,有的能贴类。常见枚举值:

比如@Override@TargetMETHOD,所以你把它贴在类上,编译器会直接报错 —— 这就是@Target的 “规则约束” 作用。

2. @Retention:规定 “注解能活多久”

@Retention的参数是RetentionPolicy枚举,决定注解在 “源码→编译→运行” 三个阶段中,能保留到哪个阶段(活多久):

public class UserService {
    // 贴注解:指定描述,默认打印时间
    @LogMethod(desc = "用户登录验证(模拟耗时300ms)")
    public void login() {
        try {
            Thread.sleep(300); // 模拟业务耗时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("登录成功:用户名=admin,密码=***");
    }
    
    // 贴注解:指定描述,不打印时间
    @LogMethod(desc = "用户退出登录", printTime = false)
    public void logout() {
        System.out.println("退出成功:清除用户Session");
    }
    
    // 没贴注解
    public void queryUser() {
        System.out.println("查询用户:id=1");
    }
    
    // 测试入口
    public static void main(String[] args) throws Exception {
        LogUtils.execute(UserService.class, "login");
        LogUtils.execute(UserService.class, "logout");
        LogUtils.execute(UserService.class, "queryUser");
    }
}

运行结果(符合预期)

plaintext

开始执行:用户登录验证(模拟耗时300ms)
登录成功:用户名=admin,密码=***
执行结束:耗时301ms

开始执行:用户退出登录
退出成功:清除用户Session
执行结束

查询用户:id=1
方法queryUser未加@LogMethod,不打印日志

四、动手实战:写一个能 “记录方法执行日志” 的自定义注解

光说不练假把式,我们来写一个实用的自定义注解@LogMethod,作用是 “自动记录方法的执行时间和描述”,步骤分 3 步:定义注解→写解析器→测试。

步骤 1:定义 @LogMethod 注解(用元注解定规则)

import java.lang.annotation.*;

// 元注解:1.只能贴在方法上;2.保留到运行时(反射要用);3.生成文档包含
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogMethod {
    // 注解属性1:方法描述,默认空字符串
    String desc() default "";
    
    // 注解属性2:是否打印执行时间,默认true
    boolean printTime() default true;
}

步骤 2:写注解解析器(用反射实现逻辑)

注解本身不会执行任何代码,必须写 “解析器”—— 用反射获取方法上的@LogMethod注解,然后在方法执行前后做日志记录。

步骤 3:测试注解效果

写一个UserService类,给方法贴@LogMethod,然后用LogUtils执行:

  • since:说明 “从哪个版本开始过时”;
  • forRemoval:说明 “未来会不会删除”(true = 会删,IDE 警告更严重)。
    public class OldTool {
        // 标记:从1.2版本开始过时,未来会删除
        @Deprecated(since = "1.2", forRemoval = true)
        public void oldMethod() {
            System.out.println("这个方法要被淘汰了");
        }
        
        // 新方法,替代oldMethod
        public void newMethod() {
            System.out.println("用我替代旧方法!");
        }
    }
    
    // 调用时:IDE会给oldMethod()标黄警告
    public class Test {
        public static void main(String[] args) {
            OldTool tool = new OldTool();
            tool.oldMethod(); // 警告:'oldMethod()' is deprecated since version 1.2 and will be removed
            tool.newMethod(); // 无警告
        }
    }

    3. @SuppressWarnings:临时 “屏蔽警告” 的 “开关”

    作用:屏蔽 IDE 或编译器弹出的 “已知但暂时无法解决的警告”,比如 “调用过时方法”“变量未使用”“未泛型化的集合”。

    比如前面调用oldMethod()时,我们知道它过时,但暂时没来得及替换,就可以用@SuppressWarnings让警告消失 —— 但注意:这是 “临时方案”,别滥用!

    正确用法:需要传一个 “警告类型” 参数,告诉它要屏蔽哪种警告,常见参数:

  • deprecation:屏蔽 “调用过时方法” 警告;
  • unused:屏蔽 “变量 / 方法未使用” 警告;
  • rawtypes:屏蔽 “集合未加泛型” 警告(如List list = new ArrayList());
  • all:屏蔽所有警告(不推荐,容易忽略重要问题)。
  • TYPE:类、接口、枚举;
  • METHOD:方法;
  • FIELD:成员变量(字段);
  • PARAMETER:方法参数;
  • CONSTRUCTOR:构造方法。
  • @Override@RetentionSOURCE:编译器用它校验完重写逻辑,就把它删了,class文件里看不到;
  • Spring 的@ControllerRUNTIME:Spring 需要在运行时扫描这些注解,把类注册成控制器,所以必须保留到运行时。

五、入门总结:注解入门就这 3 件事

到这里,注解的入门知识就讲完了。下次写代码时,别再只当@Override是 “多余的标记”—— 它是你的 “防错卫士”;也别再随意调用@Deprecated的方法 —— 它是框架给你的 “善意提醒”。下一篇我们会讲注解的进阶内容:底层原理、框架中的应用(比如 Spring 的@Autowired),以及复杂场景的自定义注解,敬请期待!

  • 分清注解和注释:注解是给程序看的,会被解析执行;注释是给人看的,会被忽略;
  • 掌握 3 个基础注解@Override防拼写错,@Deprecated标过时,@SuppressWarnings临时屏蔽警告;
  • 会写简单自定义注解:用元注解(@Target+@Retention)定规则,用反射写解析器,就能实现 “贴标签→自动执行逻辑”。

更多推荐