前言:
咱们书接上回,上次按照框架讲了
第一篇,我们讲到了:
①注解的引入(简单概述):在jdk5.0的时候
②注解与注释的区别:
- 注释 是为了帮助人类阅读代码,不会对程序的执行产生任何影响。
- 注解 是为了给编译器或运行时环境提供额外的信息,可能会影响程序的编译或运行行为。
并通过 比喻(日记和标签,贴纸)和 举代码例子 的方式帮助大家更直观的了解二者的区别
③注解的重要性:
并通过 传统方式(XML配置)和 现代方式(使用注解)代码比对的方式,带大家更直观的明白注解的好处
- 简化配置
- 提高开发效率
第二篇,我们讲到了:
一,常见的Annotation 作用:(三大作用)
- 1:生成文档相关的注解:(⭐⭐⭐)
小编给大家总结了很多常见的注解,并且通过具体的代码例子,体会注解在其中发挥的作用以及该如何正确使用
- 2:在编译时进行格式检查(JDK内置的三个基本注解)(⭐⭐⭐)
- 2.1@Override: 限定重写父类方法,该注解只能用于方法
- 2.2@Deprecated: 用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰 的结构危险或存在更好的选择
- 2.3@SuppressWarnings: 抑制编译器警告
- 3:跟踪代码依赖性,实现替代配置文件功能(小白可不看)
Servlet类(因为咱们主要是讲Java,而这个涉及到HTML了)
开始之前,大家可不可以给小编一个免费的赞或者关注(我们一起进步啊!!!)
三,元注解:(⭐⭐⭐⭐)
概述:
- 元注解(Meta-annotation)是用于注解其他注解的注解。在编程语言中,如Java、C#等,
- 注解是一种用来为代码元素(类、方法、变量等)提供额外信息或元数据的机制。
- 而元注解则是这些注解的注解,它们可以用来定义新的注解的行为和属性。(⭐⭐⭐)
- 在Java中,有几种内置的元注解,比如
@Retention
、@Target
、@Documented
和@Inherited
,它们允许开发者指定自定义注解的相关特性,例如注解的作用范围、生命周期以及是否会被编译到文档中等。 - JDK1.5 在 java.lang.annotation 包定义了 4 个标准的 meta-annotation 类型,它 们被用来提供对其它 annotation类型作说明。
大白话拆解:
你正在给一幅画做标记,比如说用红圈标出重要的部分,用蓝箭头指出需要特别注意的地方。这些标记帮助别人更好地理解这幅画。在编程中,我们也有类似的东西叫“注解”,它就像是给代码加上一些特殊的标记,告诉计算机或者其他程序员这段代码有什么特殊的意义或者应该怎么处理。
元注解就是给这些“标记”本身再加一个标记。就好比你在红圈旁边写上“这个红圈意味着重要信息”,或者在蓝箭头旁边说明“这个箭头指向需要注意的区域”。元注解就是用来解释我们创建的那些注解是什么意思,或者应该怎样使用。
官方定义使用的例子
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCustomAnnotation {
String value();
}
代码解释和总结:
- @Retention 和 @Target 就是元注解。
- @Retention 指定了注解的生命周期,这里设置为 RUNTIME 表示这个注解会在运行时仍然可用。@Target 则指定了这个注解可以应用于哪些程序元素,这里是 METHOD,表示只能用于方法上。
我给大家举个简单的栗子
创建一个名为 @NeedsTesting 的注解,当你在某个方法上使用这个注解时,就表示这个方法需要被测试。为了确保这个注解能在运行时被读取,并且只能应用于方法上,
// 告诉Java这个注解应该在运行时还能被读取
@Retention(RetentionPolicy.RUNTIME)
// 指定这个注解只能用在方法上
@Target(ElementType.METHOD)
public @interface NeedsTesting {
// 这个注解还可以带一个参数,比如测试的优先级
int priority() default 1;
}
然后你可以在你的代码中像这样使用:
public class MyClass {
@NeedsTesting(priority = 5) // 标记这个方法需要高优先级的测试
public void myMethod() {
// 方法的实现
}
}
(1)@Target:用于描述注解的使用范围
可以通过 枚举类型 ElementType 的10个常量对象来指定 :
- ETYP: 类、接口(包括注解类型)或枚举声明。
- FIELD: 字段、枚举常量。
- METHOD: 方法。
- PARAMETER: 方法参数。
- CONSTRUCTOR: 构造函数。
- LOCAL_VARIABLE: 局部变量。
- ANNOTATION_TYPE: 注解类型。
- PACKAGE: 包声明。
- TYPE_PARAMETER: 类型参数。
- TYPE_USE: 类型使用。
举个栗子:
创建一个名为 @Author 的注解,我们希望它只能用在类和方法上。我们可以这样定义:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 指定 @Author 注解只能用于类和方法
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME) // 表示该注解将在运行时保留
public @interface Author {
String name(); // 作者的名字
String date() default "2024-12-26"; // 默认日期
}
在我们的代码中像这样使用 @Author 注解:
// 正确使用:应用于类
@Author(name = "张三")
public class MyClass {
// 正确使用:应用于方法
@Author(name = "李四", date = "2024-12-25")
public void myMethod() {
// 方法的实现
}
// 错误使用:尝试应用于字段(根据 @Target 定义,这是不允许的)
// @Author(name = "王五") // 这行代码会导致编译错误
private String myField;
}
代码解释和总结:
- @Target 元注解指定了 @Author 只能用于类 (ElementType.TYPE) 和方法 (ElementType.METHOD)。如果你试着将 @Author 应用于其他类型的元素,例如字段,Java 编译器将会报错,提示你这个注解不能用于那个位置。
(2)@Retention:用于描述注解的生命周期
概述:
- @Retention 是 Java 中的一个元注解,它用来指定自定义注解在程序中存在的时间长度,也就是它的生命周期。
- 通过 @Retention,你可以控制注解是在编译时被丢弃、包含在编译后的字节码中但不加载到 JVM 中,还是在运行时仍然可用并可以通过反射机制读取。
大白话拆解:
你写了一封信(注解),然后你决定这封信可以在什么情况下被读到:
- SOURCE(源代码):这封信只在你写的草稿纸上(源代码文件)可以看到,一旦你把信寄出去(编译),这封信就消失了。
- CLASS(字节码):这封信会和你的信一起打包(编译成字节码),但是当别人收到信并打开阅读(运行程序)的时候,这封信已经被销毁了,他们看不到。
- RUNTIME(运行时):这封信不仅会和你的信一起打包,而且当别人收到并打开阅读(运行程序)的时候,他们还能看到这封信,并且可以根据信中的指示做出相应的行动(比如执行特定的操作)。
- 可以通过枚举类型RetentionPolicy的3个常量对象来指定
- SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
- 唯有RUNTIME阶段才能被反射读取到。
RetentionPolicy 的常量对象:
RetentionPolicy 枚举类型定义了3个常量,它们分别代表不同的生命周期:
- SOURCE: 注解仅存在于源代码级别,不会被编译器处理或保留在类文件中。
- CLASS: 注解会被编译器记录到类文件中,但在运行时不会被JVM保留,因此无法通过反射访问。
- RUNTIME: 注解会被编译器记录到类文件中,并且会在运行时由JVM保留,所以可以通过反射机制读取。
举个栗子:
创建一个名为 @DebugInfo 的注解,我们希望它能够在运行时被读取,以便于调试工具可以获取这些信息。我们可以这样定义:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 指定 @DebugInfo 注解将在运行时保留
@Retention(RetentionPolicy.RUNTIME)
// 指定 @DebugInfo 注解只能用于方法
@Target(ElementType.METHOD)
public @interface DebugInfo {
String value(); // 调试信息
}
我们可以在我们的代码中像这样使用 @DebugInfo 注解:
public class MyClass {
// 使用 @DebugInfo 注解添加调试信息
@DebugInfo("This method adds two numbers.")
public int add(int a, int b) {
return a + b;
}
// 一个简单的测试方法,用来读取并打印 @DebugInfo 注解的信息
public static void main(String[] args) throws Exception {
// 获取 MyClass 类的所有方法
for (java.lang.reflect.Method method : MyClass.class.getDeclaredMethods()) {
// 检查方法上是否有 @DebugInfo 注解
if (method.isAnnotationPresent(DebugInfo.class)) {
// 获取并打印 @DebugInfo 注解的值
DebugInfo debugInfo = method.getAnnotation(DebugInfo.class);
System.out.println("Debug Info for " + method.getName() + ": " + debugInfo.value());
}
}
}
}
代码解释和总结:
- @Retention(RetentionPolicy.RUNTIME) 元注解指定了 @DebugInfo 注解会在运行时保留,这意味着我们可以在运行时通过反射来读取这个注解。
- 如果我们尝试将 @Retention 改为 SOURCE 或 CLASS,那么在 main 方法中尝试读取 @DebugInfo 注解将会失败,因为注解信息不会在运行时保留。
(3)@Documented:表明这个注解应该被 javadoc工具记录。
概述:
- @Documented 是 Java 中的一个元注解,它用于指定自定义注解应该被包含在由 javadoc 工具生成的文档中。
- 默认情况下,Java 的注解不会出现在生成的 API 文档中,但如果你希望某些注解能够帮助其他开发者理解代码的功能或用法,你可以使用 @Documented 来确保这些注解会被记录到文档中。
举个栗子:
- 创建一个名为 @DeprecatedMethod 的注解,用来标记那些已经被废弃的方法,并且我们希望这个信息能出现在生成的 Javadoc 文档中。我们可以这样定义:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 指定 @DeprecatedMethod 注解应该被 javadoc 工具记录
@Documented
// 指定 @DeprecatedMethod 注解将在运行时保留
@Retention(RetentionPolicy.RUNTIME)
// 指定 @DeprecatedMethod 注解只能用于方法
@Target(ElementType.METHOD)
public @interface DeprecatedMethod {
String reason() default "未提供原因"; // 废弃的原因
}
- 在我们的代码中像这样使用 @DeprecatedMethod 注解:
/**
* 一个示例类,包含一些方法。
*/
public class MyClass {
/**
* 一个已经被废弃的方法,不建议使用。
*
* @deprecated 请使用 {@link #newMethod} 代替。
*/
@DeprecatedMethod(reason = "该方法已被新方法取代")
public void oldMethod() {
System.out.println("这是一个旧方法。");
}
/**
* 新的方法,推荐使用。
*/
public void newMethod() {
System.out.println("这是一个新方法。");
}
}
代码解释和总结:
- @Documented 元注解指定了 @DeprecatedMethod 注解应该被 javadoc 工具记录。
- 当你使用 javadoc 工具为 MyClass 生成文档时,@DeprecatedMethod 注解以及它的 reason 参数将会出现在生成的 HTML 文档中,从而帮助其他开发者了解哪些方法是不推荐使用的及其原因。
(4)@Inherited:允许子类继承父类中的注解
概述:
- @Inherited 是 Java 中的一个元注解,它用于指定自定义注解可以被子类继承。默认情况下,Java 的注解不会自动传递给子类。如果你希望某个注解能够被子类自动继承,可以在定义注解时使用 @Inherited 元注解。
举个栗子:
- 创建一个名为 @Versioned 的注解,用来标记类的版本信息,并且我们希望这个注解能够被子类自动继承。我们可以这样定义:
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 指定 @Versioned 注解可以被子类继承
@Inherited
// 指定 @Versioned 注解将在运行时保留
@Retention(RetentionPolicy.RUNTIME)
// 指定 @Versioned 注解只能用于类
@Target(ElementType.TYPE)
public @interface Versioned {
String version(); // 版本号
}
在我们的代码中像这样使用 @Versioned 注解:
// 父类带有 @Versioned 注解
@Versioned(version = "1.0")
public class ParentClass {
public void display() {
System.out.println("这是父类的方法。");
}
}
// 子类没有显式地添加 @Versioned 注解,但会自动继承父类的注解
public class ChildClass extends ParentClass {
@Override
public void display() {
System.out.println("这是子类的方法。");
}
}
// 一个简单的测试方法,用来读取并打印 @Versioned 注解的信息
public class TestInheritance {
public static void main(String[] args) {
// 获取 ParentClass 类的 @Versioned 注解
Versioned parentVersion = ParentClass.class.getAnnotation(Versioned.class);
if (parentVersion != null) {
System.out.println("ParentClass version: " + parentVersion.version());
}
// 获取 ChildClass 类的 @Versioned 注解
Versioned childVersion = ChildClass.class.getAnnotation(Versioned.class);
if (childVersion != null) {
System.out.println("ChildClass version: " + childVersion.version());
} else {
System.out.println("ChildClass did not inherit the @Versioned annotation.");
}
}
}
代码解释和总结:
- @Inherited 元注解指定了 @Versioned 注解可以被子类继承。因此,尽管 ChildClass 没有显式地添加 @Versioned 注解,它仍然会从 ParentClass 继承这个注解。
- 当你运行 TestInheritance 类的 main 方法时,你会看到 ChildClass 也显示了从 ParentClass 继承的版本信息。
四,自定义注解的使用(⭐⭐⭐⭐)
4.1声明自定义注解
语法格式:
【元注解】
【修饰符】 @interface 注解名{
【成员列表】
}
- 自定义注解可以通过四个元注解 @Retention,@Target,@Inherited,@Documented, 分别说明它的声明周期,使用位置,是否被继承,是否被生成到API文档中。
- Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声 明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型、Class 类型、enum类型、Annotation类型、以上所有类型的数组
- 可以使用 default 关键字为抽象方法指定默认返回值
- 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式 是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以省 略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名value。
举个栗子:
定义自定义注解:
import java.lang.annotation.*;
// 定义 TaskPriority 枚举,用于表示任务的优先级
public enum TaskPriority {
LOW, MEDIUM, HIGH
}
// 定义 @TaskInfo 注解
@Documented // 表明这个注解应该被 javadoc 工具记录
@Inherited // 允许子类继承父类中的注解
@Target(ElementType.TYPE) // 指定注解只能用于类
@Retention(RetentionPolicy.RUNTIME) // 指定注解将在运行时保留
public @interface TaskInfo {
// 配置参数:任务名称,默认值为 "Unnamed Task"
String name() default "Unnamed Task";
// 配置参数:任务优先级,默认值为 TaskPriority.LOW
TaskPriority priority() default TaskPriority.LOW;
// 配置参数:任务截止日期,默认值为空字符串
String deadline() default "";
// 配置参数:任务描述,默认值为空字符串
String description() default "";
}
4.2使用自定义注解
使用自定义注解:
// 父类带有 @TaskInfo 注解
@TaskInfo(
name = "Develop Feature A",
priority = TaskPriority.HIGH,
deadline = "2024-12-31",
description = "Implement the core functionality of the application."
)
public class ParentTask {
public void execute() {
System.out.println("Executing ParentTask.");
}
}
// 子类没有显式地添加 @TaskInfo 注解,但会自动继承父类的注解
public class ChildTask extends ParentTask {
@Override
public void execute() {
System.out.println("Executing ChildTask.");
}
}
// 测试类,用来读取并打印 @TaskInfo 注解的信息
public class TestTaskInfo {
public static void main(String[] args) {
// 获取 ParentTask 类的 @TaskInfo 注解
TaskInfo parentTaskInfo = ParentTask.class.getAnnotation(TaskInfo.class);
if (parentTaskInfo != null) {
printTaskInfo(parentTaskInfo, "ParentTask");
}
// 获取 ChildTask 类的 @TaskInfo 注解
TaskInfo childTaskInfo = ChildTask.class.getAnnotation(TaskInfo.class);
if (childTaskInfo != null) {
printTaskInfo(childTaskInfo, "ChildTask");
} else {
System.out.println("ChildTask did not inherit the @TaskInfo annotation.");
}
}
// 辅助方法,用来打印 TaskInfo 注解的信息
private static void printTaskInfo(TaskInfo taskInfo, String className) {
System.out.println(className + " info:");
System.out.println(" Name: " + taskInfo.name());
System.out.println(" Priority: " + taskInfo.priority());
System.out.println(" Deadline: " + taskInfo.deadline());
System.out.println(" Description: " + taskInfo.description());
}
}
大白话拆解:
你正在管理一个项目,每个任务都有名字、优先级、截止日期和描述。为了帮助团队成员更好地理解每个任务,你决定在代码中为每个任务添加一些标签(注解)。这些标签可以帮助你在编写代码时记住任务的详细信息,并且可以在生成文档时自动包含这些信息。
- @Retention:这个标签决定了它能存在多久。我们可以选择让它只存在于源代码中(SOURCE),编译后仍然保留在字节码中(CLASS),或者在程序运行时也可以访问(RUNTIME)。在这个例子中,我们选择了 RUNTIME,这样我们可以在运行时通过反射读取这些标签。
- @Target:这个标签决定了它可以贴在哪里。我们可以选择让它只能贴在类上(TYPE),方法上(METHOD),字段上(FIELD)等。在这个例子中,我们选择了 TYPE,所以它只能用在类上。
- @Inherited:这个标签告诉 Java:“如果一个类有这个标签,那么它的所有子类也会自动拥有这个标签。” 这样,如果你有一个父类的任务信息,子类也可以继承这些信息,而不需要重复定义。
- @Documented:这个标签告诉 javadoc 工具:“请把这个标签也加到生成的文档中。” 这样,其他开发者在查看文档时也能看到这些任务信息。
- 配置参数:注解中的配置参数就像标签上的信息。你可以指定任务的名字、优先级、截止日期和描述。这些参数可以用默认值,也可以在使用时指定具体的值。例如,如果我们不提供任务的名字,它将默认为 “Unnamed Task”。
4.3读取和处理自定义注解
1. 定义自定义注解
创建一个名为 @TaskInfo 的注解,用来标记任务的相关信息。这个注解将包含任务的名称、优先级(枚举类型)、截止日期(字符串),并且可以在运行时被读取和处理。
import java.lang.annotation.*;
// 定义 TaskPriority 枚举,用于表示任务的优先级
public enum TaskPriority {
LOW, MEDIUM, HIGH
}
// 定义 @TaskInfo 注解
@Retention(RetentionPolicy.RUNTIME) // 指定注解将在运行时保留
@Target(ElementType.TYPE) // 指定注解只能用于类
public @interface TaskInfo {
String name() default "Unnamed Task"; // 任务名称,默认值为 "Unnamed Task"
TaskPriority priority() default TaskPriority.LOW; // 任务优先级,默认值为 LOW
String deadline() default ""; // 任务截止日期,默认值为空字符串
String description() default ""; // 任务描述,默认值为空字符串
}
2. 使用自定义注解
在一个类中使用 @TaskInfo 注解,
// 使用 @TaskInfo 注解标记一个任务
@TaskInfo(
name = "Develop Feature A",
priority = TaskPriority.HIGH,
deadline = "2024-12-31",
description = "Implement the core functionality of the application."
)
public class TaskA {
public void execute() {
System.out.println("Executing TaskA.");
}
}
// 另一个任务,没有显式地添加 @TaskInfo 注解
public class TaskB {
public void execute() {
System.out.println("Executing TaskB.");
}
}
3. 读取和处理自定义注解
为了读取和处理这些注解,我们需要使用 Java 的反射 API 来获取注解信息并根据需要进行处理。可以创建一个工具类 TaskManager 来管理任务。
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
public class TaskManager {
// 辅助方法,用来打印 TaskInfo 注解的信息
private static void printTaskInfo(TaskInfo taskInfo, String className) {
System.out.println(className + " info:");
System.out.println(" Name: " + taskInfo.name());
System.out.println(" Priority: " + taskInfo.priority());
System.out.println(" Deadline: " + taskInfo.deadline());
System.out.println(" Description: " + taskInfo.description());
}
// 方法:读取并处理所有带有 @TaskInfo 注解的任务
public static void processTasks(List<Class<?>> taskClasses) {
for (Class<?> taskClass : taskClasses) {
// 检查类上是否有 @TaskInfo 注解
if (taskClass.isAnnotationPresent(TaskInfo.class)) {
TaskInfo taskInfo = taskClass.getAnnotation(TaskInfo.class);
printTaskInfo(taskInfo, taskClass.getSimpleName());
// 创建任务实例并执行
try {
Object taskInstance = taskClass.getDeclaredConstructor().newInstance();
if (taskInstance instanceof Task) {
((Task) taskInstance).execute();
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println(taskClass.getSimpleName() + " does not have @TaskInfo annotation.");
}
}
}
// 接口:定义任务必须实现的方法
public interface Task {
void execute();
}
}
4. 测试注解的读取和处理
最后,我们编写一个测试类 TestTaskManager,来验证 TaskManager 是否能正确读取和处理带有 @TaskInfo 注解的任务。
public class TestTaskManager {
public static void main(String[] args) {
// 创建一个任务列表,包含 TaskA 和 TaskB
List<Class<?>> tasks = new ArrayList<>();
tasks.add(TaskA.class);
tasks.add(TaskB.class);
// 调用 TaskManager 处理任务
TaskManager.processTasks(tasks);
}
}
大白话拆解:
- 你正在管理一个项目,每个任务都有名字、优先级、截止日期和描述。为了帮助团队成员更好地理解每个任务,你决定在代码中为每个任务添加一些标签(注解)。这些标签可以帮助你在编写代码时记住任务的详细信息,并且可以在运行时自动处理这些信息。
- 定义注解:我们创建了一个名为 @TaskInfo 的注解,它可以标记任务的名称、优先级、截止日期和描述。我们使用 @Retention(RetentionPolicy.RUNTIME) 来确保这个注解可以在程序运行时被读取。
- 使用注解:我们在 TaskA 类上使用了 @TaskInfo 注解,指定了任务的具体信息。而 TaskB 类没有使用这个注解,所以我们希望它不会被处理。
- 读取和处理注解:我们编写了一个 TaskManager 类,它会遍历所有任务类,检查它们是否带有 @TaskInfo 注解。如果有,它会读取注解中的信息并打印出来,然后调用任务的 execute() 方法来执行任务。如果没有注解,它会提示该任务没有相关信息。
- 测试:我们编写了一个 TestTaskManager 类来测试 TaskManager 是否能正确读取和处理带有 @TaskInfo 注解的任务。运行这个测试类时,你会看到 TaskA 的信息被打印出来,并且它的 execute() 方法被执行,而 TaskB 则没有相关信息。
ok,我们注解也彻底完结了。希望对大家有帮助。
键盘敲烂,月薪过万!!!