Skip to content

注解

注解概念

注解作用:注解不会直接影响代码逻辑,但可以被用来“标记”某些信息,从而让工具或框架“知道”该怎么处理这段代码。注解就像“标签”:你贴在代码的类、方法、字段、参数上,告诉系统或开发者,“这段代码有个特殊的用途”。不同的注解,有不同的作用,可以添加在不同的位置,有的可以写值,有的不能写值

JDK支持:注解是JDK1.5新加入的内容。

注解的产生:

java
Java web开发历程:web项目中会存在大量的配置文件,例如xml yml properties文件等;
配置文件阅读性差,编写错误不能立即提示;配置文件会增加代码的复杂程度;
    
JDK开发人员在1.5引入了注解,用于来替代配置文件;
    
早期:Java代码 + 配置文件
现在:Java代码 + 配置文件 + 注解
    
注解的优点:简化开发 提高代码可读性;
思想:约定大于配置;

Annotation类

Annotation是一个接口,所有注释类型扩展的公共接口。

构造方法

java

方法

annotationType()

java
作用:返回此注释的注释类型
    
参数:无
    
返回值:类<? extends Annotation>
    
示例:

常用注解

@override

该注解用于标识一个方法是重写(Override)父类或接口中的方法。它能帮助编译器检查你是否真正重写了父类的方法,防止拼写错误

java
class Animal {
    void speak() {
        System.out.println("Animal speaks");
    }
}

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

@FunctionalInterface

用于标注一个接口是“函数式接口”(只能有一个抽象方法),可以被 Lambda 表达式使用。

java
@FunctionalInterface
interface MyFunction {
    void execute();  // 只能有一个抽象方法
}

// 使用 Lambda 表达式实现该接口
public class Main {
    public static void main(String[] args) {
        MyFunction func = () -> System.out.println("Hello from functional interface!");
        func.execute();
    }
}

@SuppressWarnings("unused")

该注解用于告诉编译器忽略特定的警告信息。"unused" 表示忽略“未使用的变量”警告

java
public class Demo {
    // 注解可以加在方法上和变量上
    @SuppressWarnings("unused")
    public void doSomething() {
        String unusedVar = "I won't be used";
        System.out.println("Doing something");
    }
}

@Deprecated

标记某个类、方法、字段“不建议使用”,但仍然可以用。提示开发者使用新版本

java
@Deprecated
public void oldMethod() {
    System.out.println("This method is deprecated");
}

public void newMethod() {
    System.out.println("Use this method instead");
}

@SuppressWarnings

抑制编译器的警告信息,比如 “unchecked”, “deprecation”, “unused” 等

java
@SuppressWarnings("deprecation")
public void callDeprecated() {
    oldMethod(); // 不会有警告了
}

应用场景

场景示例注解
编译校验@Override, @FunctionalInterface
开发工具提示@Deprecated, @SuppressWarnings
Java EE/Spring 框架@Component, @Autowired, @RequestMapping
测试框架@Test, @Before, @After
ORM 映射@Entity, @Table, @Column
自定义逻辑(反射)自定义注解 + @Retention(RUNTIME)

元注解

元注解是用于注解“注解”的注解,也就是说,它们用来描述其他注解的行为和作用范围。

换句话说,如果你要定义一个自己的注解(如 @MyAnnotation),那么就需要用元注解来告诉编译器

java
1、这个注解应该应用到哪里?
2、它应该保留到什么时候?
3、它能否被继承?
4、能否重复使用?

常见元注解

注解名作用简述
@Target指定注解可以应用到哪些程序元素上(类、方法、字段等)
@Retention指定注解在生命周期中的保留策略(源码 / 类文件 / 运行时)
@Documented指定注解是否会包含在 Javadoc 中
@Inherited指定注解是否会被子类继承
@Repeatable允许一个注解在同一位置重复使用(Java 8+)

元注解示例

✅ 1、@Target

限定注解能用在什么位置上,比如类、方法、字段、参数等,默认不写,代表可以添加到任何位置。

java
@Target({ElementType.METHOD, ElementType.TYPE}) // 写多个位置
public @interface MyAnnotation {}

📌ElementType常用值:

枚举值说明
TYPE类、接口、枚举
FIELD成员变量
METHOD方法
PARAMETER参数
CONSTRUCTOR构造函数
LOCAL_VARIABLE局部变量
ANNOTATION_TYPE注解类型
PACKAGE

⚡ 注意:如果注解中只有一个属性值,并且属性值为value,则可以直接写值;其他的情况都必须写为 属性值 = 属性值形式

java
// @Target Java源代码内容

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    // 注解中只有一个属性值,并且属性值为value
    ElementType[] value();
}
java
@Target(ElementType.TYPE) // 直接写值
public @interface A1 {
	
}

✅ 2、@Retention

指定注解的保留策略,即它在什么时候对程序可见。

java
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

📌Retention常用值:

枚举值说明
SOURCE注解只在源码中存在,编译后被丢弃(如 @Override
CLASS(默认)编译到 class 文件中,运行时不可访问
RUNTIME编译进 class,运行时可通过反射读取(框架常用)

✅ 3. @Documented

指定注解是否出现在 Javadoc 中。

java
@Documented
public @interface MyAnnotation {
}

默认情况下,自定义注解不会出现在 Javadoc 中,除非加了 @Documented

✅ 4、@Inherited

指定一个注解是否能被子类继承(仅对类有效)。

java
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
    String value();
}
java
@Role("admin")
class Parent {}

class Child extends Parent {} // Child 也具有 @Role 注解

注意:只能用于类ElementType.TYPE),不能用于方法、字段等。

✅ 5 、@Repeatable(Java 8+)

允许一个注解在同一个位置重复使用

java
@Repeatable(Hints.class)
@interface Hint {
    String value();
}

@interface Hints {
    Hint[] value();
}
java
@Hint("hint1")
@Hint("hint2")
public class Person {
}

✅ 示例:自定义注解结合元注解使用

新建注解步骤:鼠标右键,新建JavaClass,选择Annotation。

java
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogExecution {
    String value() default "default";
}

🧪 注解的使用

✅ 1、创建元注解

java
package com.annotatePart;


import java.lang.annotation.*;

@Retention(RetentionPolicy.SOURCE) // 作用时机在源代码时
@Documented // 生成文档
@Deprecated // 过时标记
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD}) // 修饰范围为类,属性,方法
public @interface A1 {

}

✅ 2、使用注解

java
package com.annotatePart;

@A1
public class TestAnnotation1 {
    @A1
    String name;

    @A1
    public void getName(){
        System.out.println("你的名字");
    }
}

注解属性

注解中的“属性”,其实就是一组没有方法体的抽象方法,这些方法的名字就是属性名,它们可以有默认值。

示例:

java
public @interface MyAnnotation {
    String name();          // 属性1:必须赋值
    int age() default 18;   // 属性2:有默认值,可选赋值
}

这段代码定义了一个注解 @MyAnnotation,它有两个“属性”:

  • name():没有默认值,因此在使用注解时 必须赋值
  • age():有默认值 18,因此在使用时可以省略。

注解属性类型

注解中的属性(即你在注解里定义的方法)只能是编译器允许的“常量表达式类型”,而不是任意 Java 类型。

✅ 允许的属性类型:

类型类别示例
基本类型int, long, boolean, float, 等等
String"abc"
ClassClass<?>, 如 String.class
枚举类型自定义枚举,如 Gender.MALE
注解类型另一个注解,如 @MyNestedAnnotation
上述类型的数组int[], String[], Class[], MyAnnotation[]

❌ 不允许的属性类型:

类型为什么不允许?
ListMapSet因为它们是对象,在注解中不可用运行时构造对象
任意普通对象类型比如 PersonDateUser,这些不是“编译时常量”
接口或自定义类对象同样不可在注解属性中初始化,如 RunnableMyService

🧠 为什么不允许这些类型?

  1. 注解的属性值必须是编译期常量,也就是说必须在 .class 文件中能直接保存下来的值。
  2. List/Map/Set、普通对象类型并不是常量,需要运行时构造,而注解本质上是编译期工具的“标签”,不能包含运行时代码行为。

❇️ 注解中可以写方法吗

注解内部的这些所谓“方法”,本质上不是正常 Java 方法(没有方法体),它们就是属性声明

因此,注解中 不能写带方法体的方法,也不能定义构造方法或静态方法

🏷️ 注解中不能写的内容:

内容能否写?原因
普通方法注解接口不能有方法体
构造方法注解本质上是接口,不能有构造器
静态方法注解不能有 static 代码块或 static 方法
字段(变量)注解中不能定义字段,只能定义属性(即方法)

注解属性赋值

注解中的每一个属性必须赋值,否则将无法编译通过,除非给属性使用default关键字加上默认值;

如果注解中只有一个属性,并且属性名为value,那么可以直接写值,其他的情况都必须写为属性名 = 属性值;

如果属性为数组,单个元素直接写值,多个元素使用大括号包括,逗号分割进行书写;

有三种方式:

  1. 全部属性赋值
java
String name();
int age();


String [] value();
java
// 当注解属性为多个
@MyAnnotation1(name = "Tom", age = 20)

// 当注解属性为数组
@MyAnnotation2({"Tom", "20"})
  1. 只赋值必须的属性
java
@MyAnnotation(name = "Alice") // age 使用默认值
  1. 如果只有一个属性名为 value,可以简写
java
public @interface Hello {
    String value();
}
@Hello("你好") // 等价于 @Hello(value = "你好")

🧪 示例:完整注解定义和使用

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfo {
    String name();
    int age() default 20;
    Gender gender() default Gender.MALE;
    String[] tags() default {};
}

enum Gender {
    MALE, FEMALE
}

使用:

java
@PersonInfo(name = "Tom", age = 30, gender = Gender.MALE, tags = {"developer", "java"})
public class User {}

注解实例方法

通过反射获取

java
field.getAnnotation(...) 返回的不是字符串,而是注解实例对象。

注解实例对象的方法就是你在 @interface 中定义的属性

必须 @Retention(RetentionPolicy.RUNTIME) 才能在运行时通过反射获取。

如果字段上没有该注解,返回 null

示例:

1、定义注解类

java
@Retention(RetentionPolicy.RUNTIME) // 必须是 RUNTIME,否则运行时拿不到
@Target(ElementType.FIELD) // 作用于字段
public @interface UserName {
    String value() default "";
}

2、在类中使用注解

java
public class Demo {
    @UserName("张三")
    private String name;

    public static void main(String[] args) throws Exception {
        Field field = Demo.class.getDeclaredField("name");

        // 获取注解实例
        UserName username = field.getAnnotation(UserName.class);

        if (username != null) {
            System.out.println("字段上的用户名: " + username.value());
            System.out.println("注解类型: " + username.annotationType());
        } else {
            System.out.println("没有找到 UserName 注解");
        }
    }
}

🚨 重点记忆

  • field.getAnnotation(...) 返回的不是字符串,而是注解实例对象
  • 注解实例对象的方法就是你在 @interface 中定义的属性。
  • 必须 @Retention(RetentionPolicy.RUNTIME) 才能在运行时通过反射获取。
  • 如果字段上没有该注解,返回 null