Java 基础语法:注解
学习目标:
-
掌握注解的定义与使用
-
熟悉2个元注解的作用
-
掌握使用注解处理器获取注解信息
注解与注释
注释
- 单行注释//
- 多行注释/* */
- 文档注释/** */
注释的作用:
传递额外的信息进行解释说明, 给程序员看的
注释不参与编译
注释只有语法形式, 具体内容没有要求// 年龄在18-25之间, [18,25], 18<=age<=25boolean judegeAge(int age){}什么是注解
Annotation其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理
注解的作用
通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息
Annotation就像修饰符一样被使用,可用于修饰类、构造器、方法、成员变量、参数…,这些信息被存储在Annotation的“属性名=属性值”对中
注解 VS 注释
- 相同点
- 都是用来传递额外信息的
- 不同点
- 注解可以参与编译,注释不行
- 注解有使用范围,注释没有(想咋写咋写)
- 注解作为一种数据类型,跟class interface具有同等地位
注解定义
语法
权限修饰符 @interface 注解名字{ // 注解体定义 属性类型 属性名(); 属性类型 属性名(); 属性类型 属性名(); ......}举例:public @interface Override {}
属性类型:基本数据类型String类型Class类型注解类型枚举类型以及以上类型的数组形式注意:
注解不允许继承
注解和接口的关系:
注解和接口的定义都使用同一个关键字interface,而且注解体的定义也非常类似于接口中的抽象方法。
这当然不是巧合,而是因为注解本身就是一种特殊的接口。查看java.lang.annotation.Annotation接口的JDK文档,原文是:
所有注解类型都隐式扩展自该接口。但要注意,手动扩展该公共接口的接口不会定义为注解类型。还要注意此接口本身不是定义注解型。
也就是说:
- 当你使用”@interface”关键字定义一个注解类型时,它会自动实现java.lang.annotation.Annotation接口,即使你没有显式地声明这个继承关系。
- 如果使用”interface”关键字定义一个接口类型,并显式地让它继承java.lang.annotation.Annotation接口,那么这个新接口也不会被视为一个注解类型。
- java.lang.annotation.Annotation接口本身并不是一个注解类型,它只是一个普通的接口类型。
- 注解类型并不能显式地继承其他类或接口,虽然它确实隐式实现了接口java.lang.annotation.Annotation~
当然,以上概念了解即可。虽然注解和接口确实共享了同一个关键字,但它们在实际使用中具有不同的目的和功能,可谓千差万别
元注解
元注解的概念来源于元数据,什么是元数据呢?

图中框起来的一列数据是什么意思呢?表头的学校就负责解释这一列数据,所以这一列数据都代表某个学生的学校信息。
像“学校”这样的,用于解释数据的数据,就是元数据 meta data
元注解:描述修饰注解的注解(注解的注解)

常用元注解:
@Retention元注解,来定义我们自己定义的注解的保留级别.
- RetentionPolicy.RUNTIME
- RetentionPolicy.CLASS 默认
- RetentionPolicy.SOURCE
@Target元注解,注解可以作用的目标
对于注解而言,可以作用的目标:
- 整个类 ElementType.TYPE
- 成员变量 ElementType.FIELD
- 构造方法 ElementType.CONSTRUCTOR
- 成员方法 ElementType.METHOD
注解的使用(重点)
类比类对象
User user = new User("zs",20);User user2 = new User();使用的时候注解需要通过@符号进行实例化, 对每个属性都要赋值
@注解名(属性1=属性值,属性2=属性值)解释:
- ”@“可以认为相当于“new”关键字,必不可少。
- 注解相当于给Java代码打上一个标签,所以它必须要修饰Java代码的一个结构。比如修饰一整个类,一整个方法,一个成员变量等等。
- 实例化注解时,必须给注解的各个属性赋值,赋值方式是:属性名 = 属性值。如果是数组类型的注解属性,用”{}“赋值。如果有多个属性,赋值时用”,“隔开。
注意事项:
-
每个属性都要赋值
-
可以不赋值,但是要有默认值, default
-
数组形式赋值 {}
-
如果只有1个属性, 名字叫value, 可以简化赋值
-
如果属性类型是引用类型, 不能是null
示例
@MyAnnotationpublic class Test { @MyAnnotation(666) int num;
@MyAnnotation(value = 777, b = "777", c = {"777","aaa","bb"}) public void test() { }}
@interface MyAnnotation { int value() default 123;
String b() default "abc";
String[] c() default {"123"};}注解处理器(难点)
什么是注解处理器?
- 获取注解信息, 根据注解信息进行处理
如何获取注解信息?
- 通过反射获取注解信息
基本逻辑
- 获取注解所加的类的对应的Class对象
- 通过Class对象获取注解所加的类中的结构(Filed, Constructor, Method)对象
- 通过该结构对象判断是否使用了注解
- 通过该结构对象获取注解实例
- 通过注解实例获取注解信息
package _24annotation.com.cskaoyan._04handle;
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.reflect.Method;
/** * @description: * @author: 景天 * @date: 2022/10/24 17:41 **/
public class Demo { public static void main(String[] args) throws Exception{ // 获取字节码文件对象 Class<?> c = Class.forName("_24annotation.com.cskaoyan._04handle.Demo"); // 拿到方法对象 Method loginMethod = c.getDeclaredMethod("login"); // 判断方法上是否使用了注解 boolean annotationPresent = loginMethod.isAnnotationPresent(Login.class); if (annotationPresent) { // 获取注解实例 Login loginAnnotation = loginMethod.getAnnotation(Login.class); // 获取属性值 String password = loginAnnotation.password(); String username = lo ginAnnotation.username(); // 打印 System.out.println(password); System.out.println(username);
}else { System.out.println("没有使用注解"); } }
@Login public static void login(){
}}
// 定义注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface Login{ // 属性 String username() default "admin";
String password() default "123456";}练习:
通过创建自定义注解和注解处理器来实现对数据有效性的校验。
我们将创建一个名为 ValidateData 的注解,用于指定成员变量数据的最小长度、最大长度以及不满足条件时的报错信息。
然后,我们将编写一个注解处理器来实际执行这些校验。
步骤 1: 创建 Validated 注解
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD) // 应用于字段public @interface Validated { int min() default 0; // 最小长度,默认为 0
int max() default Integer.MAX_VALUE; // 最大长度,默认为 Integer 的最大值
String message() default ""; // 校验失败时的报错信息,默认为空}步骤2: 将注解加在目标成员变量
public class User { @Validated(min = 2,max = 5,message = "名字长度必须在2-5之间") private String name;
@Validated(min = 18,max = 25,message = "年龄必须在18-25之间") private int age;
public User(String name, int age) { this.name = name; this.age = age; }
}步骤 3: 创建注解处理器
注解处理器将读取 ValidateData 注解的属性,并根据这些属性执行校验逻辑。
import java.lang.reflect.Field;
/** * @description: * @author: 景天 * @date: 2023/11/14 11:44 **/
public class ValidatedHandler { public static void validate(Object obj) throws IllegalAccessException { // 获取校验对象的Class对象 Class<?> clazz = obj.getClass(); // 获取所有成员变量并遍历 检查是否需要数据验证 for (Field field : clazz.getDeclaredFields()) { // 是否使用了注解 if (field.isAnnotationPresent(Validated.class)) { field.setAccessible(true); // 获取注解实例 Validated validate = field.getAnnotation(Validated.class); // 获取成员变量取值 Object value = field.get(obj); // 成员变量类型不确定 做个分类处理 if (value instanceof String) { // 对String类型的处理 checkStringLength((String) value, validate); } else if (value instanceof Object[]) { // 对数组的处理 checkArrayLength((Object[]) value, validate); }else { // 对数值类型的处理 checkIntRange(((int) value),validate); } // 可以添加对更多类型的支持 // ..... } } }
private static void checkIntRange(int value, Validated validate) { if (value < validate.min() || value > validate.max()) { throw new IllegalArgumentException(validate.message()); } }
private static void checkStringLength(String value, Validated validate) { if (value.length() < validate.min() || value.length() > validate.max()) { throw new IllegalArgumentException(validate.message()); } }
private static void checkArrayLength(Object[] value, Validated validate) { if (value.length < validate.min() || value.length > validate.max()) { throw new IllegalArgumentException(validate.message()); } }}步骤 4: 使用注解和处理器进行测试
/** * @description: * @author: 景天 * @date: 2023/11/14 11:55 **/
public class ValidatedTest { public static void main(String[] args) { User user = new User("zsss", 20);
try { ValidatedHandler.validate(user); System.out.println("校验通过"); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }}注解的使用场景
在 Java 中,注解被广泛应用于多种场景,提供了一种用于添加元数据到代码中的方式。注解不会直接影响代码的操作,但可以被工具、库和框架用于生成代码、进行编译时检查或在运行时处理。以下是一些常见的 Java 注解使用场景:
- 编译时检查:
- 用于提高代码质量,如
@Override确保方法覆盖,@Deprecated表示方法过时,@SuppressWarnings抑制编译器警告。
- 用于提高代码质量,如
- 框架和库:
- Web 框架如 Spring 使用注解来定义路由、请求方法、依赖注入等,例如
@Controller,@RequestMapping,@Autowired。
- Web 框架如 Spring 使用注解来定义路由、请求方法、依赖注入等,例如
- 测试代码:
- 单元测试框架如 JUnit 使用注解定义测试方法、测试前后的操作,例如
@Test
- 单元测试框架如 JUnit 使用注解定义测试方法、测试前后的操作,例如
- 代码生成:
- 自动生成代码,如 Lombok 库使用
@Getter,@Setter,@AllArgsConstructor等注解来自动生成 getter、setter 方法和构造函数。
- 自动生成代码,如 Lombok 库使用
- 配置和设置:
- 在 Spring 和其他依赖注入框架中,用于配置和设置应用程序,如
@Configuration,@Bean。
- 在 Spring 和其他依赖注入框架中,用于配置和设置应用程序,如
- 拦截器和过滤器:
- 在 AOP(面向切面编程)中,用于定义切点和通知,如
@Aspect,@Before,@After。
- 在 AOP(面向切面编程)中,用于定义切点和通知,如
- 自定义业务逻辑:
- 创建自定义注解来实现特定的业务逻辑,如验证、日志记录等。
Java 注解提供了一种强大的机制来简化代码、提高可读性和维护性,并支持诸如配置管理、数据校验和框架开发等高级功能。通过注解,开发者可以将关键信息嵌入到代码的声明性部分,而不是执行性代码中。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!