Skip to main content

注解

注解概念

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

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

注解的产生:

Java web开发历程:web项目中会存在大量的配置文件,例如xml yml properties文件等;
配置文件阅读性差,编写错误不能立即提示;配置文件会增加代码的复杂程度;

JDK开发人员在1.5引入了注解,用于来替代配置文件;

早期:Java代码 + 配置文件
现在:Java代码 + 配置文件 + 注解

注解的优点:简化开发 提高代码可读性;
思想:约定大于配置;

Annotation类

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

构造方法


方法

annotationType()

作用:返回此注释的注释类型

参数:无

返回值:类<? extends Annotation>

示例:

常用注解

@override

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

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

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

@FunctionalInterface

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

@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" 表示忽略“未使用的变量”警告

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

@Deprecated

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

@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” 等

@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),那么就需要用元注解来告诉编译器

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

常见元注解

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

元注解示例

✅ 1、@Target

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

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

📌ElementType常用值:

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

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

// @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();
}
@Target(ElementType.TYPE) // 直接写值
public @interface A1 {

}

✅ 2、@Retention

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

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

📌Retention常用值:

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

✅ 3. @Documented

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

@Documented
public @interface MyAnnotation {
}

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

4、@Inherited

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

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
String value();
}

@Role("admin")
class Parent {}

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

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

✅ 5 、@Repeatable(Java 8+)

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

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

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

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

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

import java.lang.annotation.*;

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

🧪 注解的使用

1、创建元注解

package com.annotatePart;


import java.lang.annotation.*;

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

}

2、使用注解

package com.annotatePart;

@A1
public class TestAnnotation1 {
@A1
String name;

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

注解属性

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

示例:

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. 全部属性赋值
String name();
int age();


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

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

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

@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
}

使用:

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

注解实例方法

通过反射获取

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

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

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

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

示例:

1、定义注解类

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

2、在类中使用注解

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