Java基础知识(十四)

注解

一、注解的基本概念

1. 定义

注解(Annotation)是 Java SE 5.0 引入的一种特殊语法元素,它以@符号开头,用于为 Java 代码(类、方法、字段、参数等)添加元数据(描述数据的数据)。这些元数据本身不直接影响代码的执行逻辑,但可以被编译器、工具或运行时环境读取和使用,实现如代码检查、自动生成代码、配置注入等功能。

2. 核心作用

  • 编译期检查 :如@Override注解,编译器会检查被标注的方法是否真的重写了父类方法,若不存在则报错。
  • 生成文档 :配合 Javadoc 工具,@param@return等注解可自动生成规范的 API 文档。
  • 运行时处理 :如 Spring 框架的@Autowired,在程序运行时通过反射读取注解,实现依赖注入。
  • 代码生成 :如 Lombok 的@Data,在编译期自动生成gettersettertoString()等方法,减少冗余代码。

二、Java 内置注解

Java 提供了 5 个内置的标准注解,直接用于修饰代码元素,无需额外定义:

注解名称 作用场景 核心功能
@Override 方法 标记方法为重写父类(或实现接口)的方法,编译器校验方法签名是否匹配,避免拼写错误。
@Deprecated 类、方法、字段、参数等 标记元素已过时,编译器会对使用该元素的代码发出警告,提醒开发者使用替代方案。
@SuppressWarnings 类、方法、字段等 抑制编译器产生的特定警告(需指定警告类型),如@SuppressWarnings("unchecked")抑制泛型未检查警告。
@SafeVarargs 可变参数方法(JDK 7+) 标记方法为 "安全的可变参数",抑制编译器对泛型可变参数的 "非安全操作" 警告。
@FunctionalInterface 接口(JDK 8+) 标记接口为函数式接口(仅含一个抽象方法),编译器校验接口是否符合函数式接口规范,支持 Lambda 表达式。

示例:内置注解的使用

复制代码
import java.util.ArrayList;
import java.util.List;

public class BuiltInAnnotationDemo {
    public static void main(String[] args) {
        // 使用@Deprecated标记的过时方法
        OldClass oldObj = new OldClass();
        oldObj.obsoleteMethod(); // 编译器警告:方法已过时

        // 使用@SuppressWarnings抑制泛型警告
        @SuppressWarnings("unchecked")
        List<String> list = new ArrayList(); // 无泛型检查警告
        list.add("Java Annotation");
    }
}

class OldClass {
    // 标记方法已过时,并提示替代方案
    @Deprecated(since = "1.2", forRemoval = true)
    public void obsoleteMethod() {
        System.out.println("This method is obsolete. Use newMethod() instead.");
    }

    public void newMethod() {
        System.out.println("This is the new recommended method.");
    }
}

// 标记为函数式接口,仅含一个抽象方法
@FunctionalInterface
interface MyFunctionalInterface {
    void doSomething(String msg);
}

三、元注解(Meta-Annotation)

元注解是用于定义其他注解的注解,即 "注解的注解"。Java 提供了 6 个元注解,用于控制自定义注解的生命周期、作用范围等特性:

元注解名称 作用 可选属性值
@Target 限定自定义注解可修饰的代码元素类型(如类、方法、字段等) TYPE(类 / 接口)、METHOD(方法)、FIELD(字段)、PARAMETER(参数)等
@Retention 定义自定义注解的生命周期(即注解信息保留到哪个阶段) SOURCE(仅源码)、CLASS(源码 + 字节码)、RUNTIME(源码 + 字节码 + 运行时)
@Documented 标记自定义注解会被Javadoc 工具提取到 API 文档中(默认不提取) 无属性,仅作为标记
@Inherited 标记自定义注解可被子类继承(仅对类注解有效,方法 / 字段注解不继承) 无属性,仅作为标记
@Repeatable 允许自定义注解在同一元素上重复标注(JDK 8+) 属性值为 "存储该重复注解的容器注解类"
@Native 标记注解关联的字段为原生方法(Native Method)依赖的字段(JDK 8+) 无属性,仅作为标记,较少使用

关键元注解详解

1. @Target:控制注解的作用范围

必须指定,否则自定义注解可修饰所有代码元素。示例:

复制代码
// 限定注解仅可修饰"类/接口"和"方法"
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
    // 注解属性...
}
2. @Retention:控制注解的生命周期

这是最核心的元注解之一,直接决定注解能否在运行时被读取(如通过反射):

  • SOURCE :仅存在于源码中,编译成字节码时被删除(如@Override)。
  • CLASS:存在于源码和字节码中,但运行时 JVM 不加载,无法通过反射读取(默认值)。
  • RUNTIME :存在于源码、字节码和运行时,可通过反射读取(如 Spring 的@Autowired)。

示例:

复制代码
// 注解保留到运行时,支持反射读取
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
    // 注解属性...
}

四、自定义注解

1. 定义语法

自定义注解使用@interface关键字声明,格式如下:

复制代码
// 元注解(可选,但@Target和@Retention通常必加)
[元注解1]
[元注解2]
public @interface 注解名称 {
    // 注解属性(格式:类型 属性名() [default 默认值];)
    类型 属性名1() [default 默认值1];
    类型 属性名2() [default 默认值2];
    // ...
}

2. 注解属性规则

  • 属性类型:只能是基本数据类型(int、boolean 等)、String、Class、枚举(enum)、其他注解或以上类型的数组。
  • 默认值:若属性无默认值,使用注解时必须显式赋值;若有默认值,可省略赋值。
  • 特殊属性 value :若注解只有一个属性且名为value,赋值时可省略 "value=",直接写值;若为数组且只有一个元素,可省略大括号。

3. 自定义注解示例

示例 1:简单的类 / 方法注解
复制代码
import java.lang.annotation.*;

// 限定注解可修饰类和方法,保留到运行时,支持文档提取
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    // 带默认值的属性
    String author() default "Unknown";
    // 无默认值的属性(使用时必须赋值)
    String version();
    // 数组类型属性
    String[] tags() default {};
}

// 使用自定义注解修饰类(version必须赋值,author和tags用默认值)
@MyAnnotation(version = "1.0")
class MyClass {
    // 使用自定义注解修饰方法(显式赋值所有属性)
    @MyAnnotation(author = "Alice", version = "1.1", tags = {"method", "demo"})
    public void myMethod() {
        System.out.println("This is a method annotated with MyAnnotation.");
    }
}
示例 2:支持重复标注的注解(@Repeatable)
复制代码
import java.lang.annotation.*;

// 1. 定义"重复注解的容器类"
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRepeatableAnnotations {
    MyRepeatableAnnotation[] value(); // 存储重复的注解
}

// 2. 定义可重复的注解(指定容器类)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyRepeatableAnnotations.class) // 关键:关联容器类
public @interface MyRepeatableAnnotation {
    String value(); // 单个属性value
}

// 3. 使用:同一方法上重复标注
class RepeatableDemo {
    // 重复标注(JDK 8+支持)
    @MyRepeatableAnnotation("Tag1")
    @MyRepeatableAnnotation("Tag2")
    @MyRepeatableAnnotation("Tag3")
    public void testRepeatable() {
        System.out.println("Method with repeated annotations.");
    }
}

五、注解的解析(读取与使用)

注解的元数据需要通过 "解析" 才能发挥作用,解析方式分为编译期解析运行时解析

1. 编译期解析

由编译器或注解处理器(Annotation Processor)在编译阶段读取注解,通常用于:

  • 代码检查(如@Override的语法校验)。
  • 自动生成代码(如 Lombok、ButterKnife)。
示例:自定义注解处理器(简化版)

注解处理器需继承AbstractProcessor,并通过@SupportedAnnotationTypes指定处理的注解:

复制代码
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;

// 声明处理器支持的注解和Java版本
@SupportedAnnotationTypes("com.example.MyAnnotation") // 自定义注解的全类名
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
    // 编译时处理注解的核心方法
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 遍历所有被@MyAnnotation标注的元素
        for (TypeElement annotation : annotations) {
            roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> {
                // 读取注解属性
                MyAnnotation myAnn = element.getAnnotation(MyAnnotation.class);
                String author = myAnn.author();
                String version = myAnn.version();
                // 输出注解信息(编译时控制台可见)
                System.out.printf("Processing %s: author=%s, version=%s%n", 
                                 element.getSimpleName(), author, version);
            });
        }
        return true; // 表示注解已被处理,无需其他处理器处理
    }
}

2. 运行时解析

通过反射(Reflection) 在程序运行时读取@Retention(RetentionPolicy.RUNTIME)级别的注解,动态处理逻辑(如 Spring、MyBatis 的核心机制)。

反射解析注解的核心 API
API 方法 作用
Class.getAnnotation(Class) 获取类上的指定注解
Method.getAnnotation(Class) 获取方法上的指定注解
Field.getAnnotation(Class) 获取字段上的指定注解
AnnotatedElement.isAnnotationPresent(Class) 判断元素是否被指定注解标注
示例:运行时反射解析注解
复制代码
import java.lang.reflect.Method;

public class AnnotationParser {
    public static void main(String[] args) throws NoSuchMethodException {
        // 1. 解析类上的注解
        Class<MyClass> clazz = MyClass.class;
        if (clazz.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation classAnn = clazz.getAnnotation(MyAnnotation.class);
            System.out.println("Class Annotation Info:");
            System.out.printf("  Author: %s%n  Version: %s%n  Tags: %s%n",
                    classAnn.author(), classAnn.version(), String.join(",", classAnn.tags()));
        }

        // 2. 解析方法上的注解
        Method method = clazz.getMethod("myMethod");
        if (method.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation methodAnn = method.getAnnotation(MyAnnotation.class);
            System.out.println("\nMethod Annotation Info:");
            System.out.printf("  Author: %s%n  Version: %s%n  Tags: %s%n",
                    methodAnn.author(), methodAnn.version(), String.join(",", methodAnn.tags()));
        }

        // 3. 解析重复注解(需通过容器类获取)
        Class<RepeatableDemo> repeatClazz = RepeatableDemo.class;
        Method repeatMethod = repeatClazz.getMethod("testRepeatable");
        // 方式1:通过容器类获取所有重复注解
        MyRepeatableAnnotations container = repeatMethod.getAnnotation(MyRepeatableAnnotations.class);
        System.out.println("\nRepeated Annotations Info:");
        for (MyRepeatableAnnotation ann : container.value()) {
            System.out.printf("  Tag: %s%n", ann.value());
        }
        // 方式2:直接获取重复注解(JDK 8+支持)
        MyRepeatableAnnotation[] repeatAnnotations = repeatMethod.getAnnotationsByType(MyRepeatableAnnotation.class);
        for (MyRepeatableAnnotation ann : repeatAnnotations) {
            System.out.printf("  Tag (direct): %s%n", ann.value());
        }
    }
}

六、注解的应用场景

1. 框架开发(最核心场景)

  • Spring@Autowired(依赖注入)、@Controller(标记控制器)、@RequestMapping(映射请求路径)等。
  • MyBatis@Mapper(标记 Mapper 接口)、@Select(指定查询 SQL)等。
  • JUnit@Test(标记测试方法)、@BeforeEach(测试前执行)等。

2. 减少冗余代码

  • Lombok@Data(生成getter/setter/toString)、@NoArgsConstructor(生成无参构造)、@Slf4j(注入日志对象)等。
  • ButterKnife (已过时,被 ViewBinding 替代):@BindView(绑定 XML 控件)。

3. 代码检查与规范

  • 自定义注解 + 处理器,校验代码是否符合团队规范(如方法命名必须以do开头、字段必须加注释等)。
  • 第三方工具如 Checkstyle,通过注解控制代码检查规则。

4. 权限控制与日志记录

  • 自定义@Permission注解,标记方法需特定权限,运行时通过 AOP(面向切面编程)拦截并校验权限。
  • 自定义@Log注解,标记方法需记录日志,AOP 拦截方法调用并自动打印入参、返回值、耗时等信息。
相关推荐
sc_yhsheng2 小时前
18j621-3通风天窗图集pdf(免费高清版)
windows·pdf
烟锁池塘柳02 小时前
【已解决,亲测有效】解决使用Python Matplotlib库绘制图表中出现中文乱码(中文显示为框)的问题的方法
开发语言·python·matplotlib
周小码2 小时前
llama-stack实战:Python构建Llama应用的可组合开发框架(8k星)
开发语言·python·llama
pianmian12 小时前
Spring 项目骨架
java·后端·spring
IT学长编程2 小时前
计算机毕业设计 基于Hadoop的南昌房价数据分析系统的设计与实现 Python 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试
大数据·hadoop·python·毕业设计·课程设计·毕业论文·豆瓣电影数据可视化分析
麦兜*2 小时前
Docker 部署 MongoDB:单节点与副本集的最佳实践
java·spring boot·mongodb·spring cloud·docker·容器·maven
小小怪KO3 小时前
分布式锁解决集群下一人一单超卖问题
java·分布式·tomcat·后端开发·实习·黑马点评
郑洁文3 小时前
豆瓣网影视数据分析与应用
大数据·python·数据挖掘·数据分析
程序员TNT3 小时前
Shoptnt 促销计算引擎详解:策略模式与责任链的完美融合
linux·windows·策略模式