# Java 注解 的使用文档 *Java技术栏* Java注解(Annotation)是Java编程语言中的一个重要特性,它允许程序员在代码中添加元数据,用于描述代码的行为或提供额外的信息。 ## 目录 [TOC]  ## 介绍 Java注解是一种元数据形式,可以被添加到Java代码中的各种元素(类、方法、字段等)上,以提供关于这些元素的额外信息。注解是在Java 5中引入的一项特性,它们不直接影响代码的执行,而是提供了一种机制来对代码进行标记和解释。 ## 案例演示 ### 内置注解的使用 通常以@符号开头,放到注解目标的前面。Java提供了一些内置的注解。 ``` @Override:该注解代表重写父类的方法 @Deprecated:使用@Deprecate的方法会出现一条删除线,表示该方法过时了,但是不影响使用 @SuppressWarning:指示编译器去忽略注解中声明的警告 @Retention:标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问 @Documented:标记这些注解是否包含在用户文档中 @Target:标记这个注解应该是哪种 Java 成员。 @Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类) ``` 从 Java 7 开始,额外添加了 3 个注解: ``` @SafeVarargs:忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告 @FunctionalInterface:Java 8 开始支持,标识一个匿名函数或函数式接口。 @Repeatable:Java 8 开始支持,标识某注解可以在同一个声明上使用多次。 ``` #### Override 演示 Override注解能够确保子类中的方法覆盖了父类中具有相同方法签名的有效方法。当你在子类中使用@Override注解时,编译器会检查父类中是否存在具有相同方法签名的有效方法。如果存在,则认为该方法是正确覆盖了父类中的方法;如果不存在,则编译器会报错,提示该方法无法覆盖父类中的方法。 使用Override注解可以提高代码的可读性和可维护性。它可以帮助开发人员更容易地理解代码中的行为,并且可以确保子类中的方法正确地覆盖了父类中的方法,避免了因为方法签名不一致而导致的错误。此外,编译器也可以通过Override注解来检查代码中的潜在错误,并提供更好的代码提示和帮助。 总之,Override 注解是一种非常有用的工具,可以帮助开发人员编写更加健壮、可读性和可维护性的代码。 ##### 创建父类 由于这个注解涉及到继承相关知识,在这里我们创建了两个类,一个类是父类 一个类是子类,父类中包含一个 run1 函数,在子类中包含两个函数,分别是 run1 run2,下面就是结构。 ```Java public static void main(String[] args) { new son().run1(); } public static class father { public void run1() { System.out.println("father.run1 method"); } } public static class son extends father { public void run1() { System.out.println("son.run1 method"); } public void run2() { System.out.println("son.run2 method"); } } } ``` ##### 为子类中的函数加上 Override 注解 在下面可以看到 子类中发生了错误,并且此错误在代码中就被检测了出来,避免了运行时异常需要返回找 bug 的繁琐步骤,这就是 Override 注解的有趣之处,**它实现了被注解函数是否为重写函数的检查操作**!  #### Deprecated 演示 Deprecated注解是一种标记注解,用于指示某个类、方法或字段已被弃用,不应再被使用1。 Deprecated注解的主要功能是警告程序员,某一特定的结构已经被废弃,不应再使用,应该使用替代的更先进的结构。程序员在使用Deprecated注解时,可以指定一个新版本号或者使用替代结构,以便警告程序员避免使用废弃的结构。如果该结构已被废弃,则编译器将发出一个警告,提示程序员应该使用替代的结构。 ##### 基本示例 在Java中,Deprecated注解可以标注在类、字段和方法上。使用方法是在需要标记为已弃用的类、方法和字段上添加@Deprecated注解即可。 ``` @Deprecated public class OldClass { // ... } @Deprecated public void oldMethod() { // ... } ``` 在这个示例中,我们使用@Deprecated注解标记了一个类(OldClass)和一个方法(oldMethod)。这意味着这两个元素已经被弃用,不应再被使用。如果其他程序员尝试使用这些元素,编译器会发出警告,提示他们这些元素已经废弃,应该使用替代的元素。 ##### 注解的参数 Java 9中的@Deprecated注解添加了两个新参数或属性,分别是Since和forRemoval。这两个参数是可选的,带有默认值。Since参数指定API已弃用的版本,forRemoval参数指定是否打算在未来版本中删除该API,下面是一些示例代码。 ``` @Deprecated(since = "1.0", forRemoval = true) public class OldClass { // ... } @Deprecated(since = "1.0", forRemoval = false) public void oldMethod() { // ... } ``` 在这个示例中,我们为类(OldClass)和方法(oldMethod)的Deprecated注解添加了参数。Since参数指定API已弃用的版本为1.0,forRemoval参数指定是否打算在未来版本中删除该API。如果其他程序员尝试使用这些元素,编译器会发出警告,提示他们这些元素已经废弃,应该使用替代的元素。 #### SuppressWarning 演示 SuppressWarning(SuppressWarnings)是Java语言提供的一个元注解(meta-annotation),它用于抑制编译器产生某些类型的警告。 当我们在代码中使用@SuppressWarnings注解时,编译器将不会为注解所指定的警告类型产生警告信息。使用@SuppressWarnings注解可以避免一些不必要的警告,使我们的代码更加干净、简洁。但是需要注意的是,使用@SuppressWarnings注解并不能解决潜在的问题,而只是简单地抑制了编译器产生警告信息。因此,我们不能过度依赖@SuppressWarnings注解,而应该尽可能地解决代码中的问题。 ##### 注解的参数 ``` unchecked:用于抑制未经检查的警告,例如在使用泛型时类型安全性检查的警告。 deprecation:用于抑制使用已过时的API的警告。 serialVersionUID:当在可序列化的类上缺少 serialVersionUID 定义时的警告。 fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。 path:在类路径、源文件路径等中有不存在的路径时的警告。 finally:任何 finally 子句不能正常完成时的警告。 all:关于以上所有情况的警告。 ``` SuppressWarning注解可以用于类和函数上。当它被用于类上时,它将对类中的所有方法都生效。当它被用于函数上时,它只对该函数生效。 ``` public class MAIN { // 使用了这个注解 main 函数有关泛型的不规范使用问题就不会报警告了 @SuppressWarnings("all") public static void main(String[] args) { System.out.println("run!!"); final father father = new father(); // 为什么这里不允许注解 father.setValue(1024); System.out.println(father.getValue()); } public static class father<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } } ``` #### FunctionalInterface 的演示 @FunctionalInterface注解的作用是用来检测函数式接口是否正确。因为函数式接口只能有一个抽象方法,为了避免使用检测注解,和 Override 非常相似,都是用来进行检查操作的,成功后进行编译。 函数式接口适用于函数式编程,在Java函数式编程的体现就是Lambda:()->{}。函数式接口作为方法的返回值类型。 ##### 使用示例 ``` @FunctionalInterface public interface father2 { void run1(); } // 在这里会报错,注解检查出来了不正确的地方 @FunctionalInterface public interface father1 { void run1(); void run2(); } ``` ### 自定义注解的使用 #### 初识注解结构 否则自定义注解类具有一些结构,官方中具有的内置注解类中也是按照这个格式来的,下面就是一个注解类的基本格式。 ``` 权限修饰(例如 public) @interface 注解名称(例如 MyTest) { } ``` 按照基本的格式定义了一个注解类之后我们可以直接在函数中和类中使用这个注解。 ```java @MyTest public class MAIN { @MyTest public static void test() { } } ``` 值得一提的是注解在编译之后它会成为一个接口的Java文件,所以注解的本质其实就是一个接口,在我们编译好的文件中我们定义的注解`MyTest` 会成为下面这个样子可以看到它是一个接口实现了Java中的 `Annotation` 接口。 ``` public interface MyTest extends java.lang.annotation.Annotation{ } ``` #### 注解中的抽象方法(注解的属性) 既然我们知道了注解和接口之间的关系,那么接口中可以定义的东西在注解中也可以定义,例如接口中的字段和方法等,在这里我们来演示在 注解中的属性和方法定义。 需要注意的是,在注解中,抽象方法会被称之为属性。 ##### 定义抽象方法 ``` package top.lingyuzhao; public @interface MyTest { /** * @return 一个测试的字符串 */ String getName(); /** * @return 一个测试的字符串 如果注解被调用的时候没有传入参数,那么就会返回这个默认的字符串 */ String getDefStr() default "默认的 DefStr"; } ``` ##### 在注解调用时输入抽象方法返回值 ``` package top.lingyuzhao; @MyTest(getName = "这是在类中的注解", getDefStr = "默认字符串被覆盖了") public class MAIN { @MyTest(getName = "这是在方法中的注解") public static void test() { } } ``` ##### 省去属性名的赋值 我们现在知道调用一个注解需要传递注解中的属性名字,并为属性赋值,而在注解的属性设置中,其实是可以不去书写属性名字的,但是需要满足下面两个条件 ``` 属性名字是 value 只有一个必须要被设置的属性 ``` 下面是一个示例 ```java package top.lingyuzhao; public @interface MyTest { /** * @return 一个测试的字符串 */ String value(); /** * @return 一个测试的字符串 如果注解被调用的时候没有传入参数,那么就会返回这个默认的字符串 */ String getDefStr() default "默认的 DefStr"; } ``` 下面就是 MAIN 中的调用注解的结果 ```java package top.lingyuzhao; // 如果注解中设置的属性不为 1 个 则需要显式的指定 value 参数名字 @MyTest(value = "这是在类中的注解", getDefStr = "默认字符串被覆盖了") public class MAIN { // 如果注解中必须要设置的属性只有一个并且属性名为value 我们可以不去指定参数名字 // 因为它会自动绑定到 value 属性上 @MyTest("这是在方法中的注解") public static void test() { } } ``` #### 元注解 元注解是一种用来描述注解的注解,例如描述注解类能够应用的位置是类还是函数,接下来就是有关元注解的使用的示例。 ##### target 描述注解的作用域 target 可以作用在注解上它的效果就是描述注解能够使用的位置,这个注解接收一个 `java.lang.annotation.ElementType` 参数的枚举做参数,这个枚举中包含的很多项目的描述如下所示。 在Java中,`java.lang.annotation.ElementType` 是一个枚举类型,用于指定注解可以应用于哪些元素上。其包含以下7个项目: ``` CONSTRUCTOR:用于描述构造器。 FIELD:用于描述域。 LOCAL_VARIABLE:用于描述局部变量。 METHOD:用于描述方法。 PACKAGE:用于描述包。 PARAMETER:用于描述参数。 TYPE:用于描述类、接口(包括注解类型)或enum声明。 ``` 那么接下来,我们想要实现一个将注解只能用于在 类字段 以及 类方法中,可以像下面一样使用。 ```java import java.lang.annotation.ElementType; import java.lang.annotation.Target; // 在这里我们指定的就是 这个注解只能用于方法 和 类的字段中 @Target({ ElementType.METHOD, ElementType.FIELD }) public @interface MyTest { int value(); } ``` 在 MAIN 中的使用如下所示 ```java @MyTest(0) public class MAIN1 { @MyTest(0) int data = 1024; @MyTest(0) public static void main(String[] args) { } } ``` ##### retention 描述注解的作用周期(仅仅在什么阶段生效) 注解这个东西可以包含在一个Java程序的不同阶段Java程序有三个阶段,分别是源码 编译 以及运行时,它接收一个枚举参数,在枚举参数中有这三个阶段的枚举项,当我们指定了一个项目之后,它只会在我们指定的时候存在,下面我们将使用它进行一个演示这个注解只会在源码中存在编译之后将不存在。 ```java import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; // 这个可以实现指定我们的注解要保留在哪个阶段 在这里是只会在源码中包含 @Retention(RetentionPolicy.SOURCE) public @interface MyTest { int value(); } ``` 下面就是在 MAIN 中的使用 ``` @MyTest(0) public class MAIN1 { @MyTest(0) int data = 1024; @MyTest(0) public static void main(String[] args) { } } ``` 可以看到这个里面是有注解的存在的,接下来我们将会把它编译然后再将反编译出来的源码展示一下,下面展示的就是我们反编译之后的字节码的数据,可以看到里面已经没有注解的存在,说明我们的注解仅仅保存在源码阶段。 ```java // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // public class MAIN1 { int data = 1024; public MAIN1() { } public static void main(String[] args) { } } ``` ##### Documented 描述注解是否被添加到 API 文档中 在Java中有一种API文档它叫做 JavaDoc ,这个文档的生成非常有趣,和源码中的数据息息相关,针对被注解的类在API 文档中是显示不出来的被注解的,如果希望它显示出来,我们就需要按照下面的方法去操作。 ###### 1. 在 MyTest 注解中使用 Documented ```java import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.SOURCE) // 标志 MyTest 在Java Doc 中可以显示出来 @Documented public @interface MyTest { int value(); } ``` ###### 2. 在 MAIN 中使用 MyTest 注解 ```java @MyTest(0) public class MAIN1 { @MyTest(0) int data = 1024; @MyTest(0) public static void main(String[] args) { } } ``` ###### 3. 生成 JavaDoc 并查看MAIN中的注解是否显示  ##### Inherited 标志注解的自动继承 假设我们现在有一个类叫做teacher teacher类中有一个注解叫做 MyTest 现在我们使用了一个新的类叫做 zhao,并且我们让这个类继承了 teacher 他是teacher的子类,这个时候,如果希望 MyTest 注解可以作用在 teacher 以及 其子类中 就可以使用 Inherited 注解实现自动的注解标志继承。 ###### 在注解类中使用 Inherited ```java import java.lang.annotation.*; @Documented // 表示这个类会被自动的继承 @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface MyTest { int value(); } ``` ###### 在 MAIN1 中使用 MyTest 注解 ```java @MyTest(0) public class MAIN1 { @MyTest(0) public String fun() { return "MAIN1"; } } ``` ###### 构建一个 MAIN1 的子类 MAIN 2 ```java public class MAIN2 extends MAIN1 { } ``` ### Java 注解操作 #### 获取到注解中的方法的值 这里的代码 与 ` Inherited 标志注解的自动继承` 小节中一致。 ```java import java.lang.annotation.Annotation; import java.lang.reflect.Method; public class MAIN2 extends MAIN1 { public static void main(String[] args) throws NoSuchMethodException { // 获取到 MAIN2 的 class 对象 MAIN2 main2 = new MAIN2(); Class<? extends MAIN2> main2Class = main2.getClass(); // 获取到 MAIN2 中的类的注解 for (Annotation annotation : main2Class.getAnnotations()) { if (annotation instanceof MyTest) { MyTest annotation1 = (MyTest) annotation; System.out.println(annotation1.annotationType() + " value = " + annotation1.value()); } } // 获取到 MAIN2 中 fun 方法的注解 首先需要获取到这个方法的 Class 文件 Method run = main2Class.getMethod("fun"); // 获取到注解对象 for (Annotation annotation : run.getAnnotations()) { if (annotation instanceof MyTest) { MyTest annotation1 = (MyTest) annotation; System.out.println(annotation1.annotationType() + " value = " + annotation1.value()); } } } } ``` 下面就是计算之后的结果 ``` interface MyTest value = 0 interface MyTest value = 1 进程已结束,退出代码0 ``` ### 注解处理器 在Java开发中,注解处理器(Annotation%20Processor)是一种处理源代码中注解的工具。它可以通过解析源代码中的注解,生成新的源代码、配置文件或者其他资源文件。本文将介绍如何使用Java注解处理器,并逐步指导刚入行的开发者实现一个简单的Java注解处理器。 #### 实现一个注解类 ```java package top.lingyuzhao; public @interface MyTest { /** * @return 一个测试的字符串 */ String value(); /** * @return 一个测试的字符串 如果注解被调用的时候没有传入参数,那么就会返回这个默认的字符串 */ String getDefStr() default "默认的 DefStr"; } ``` ------ ***操作记录*** 作者:[root](http://www.lingyuzhao.top//index.html?search=1 "root") 操作时间:2024-01-28 16:00:06 星期日 事件描述备注:保存/发布 中国 天津 [](如果不需要此记录可以手动删除,每次保存都会自动的追加记录)