一、Java 注解有哪些?
Java 注解,也叫元数据,又称 Java 标注,即一种描述数据的数据。注解是 JDK1.5 版本开始引入的一种注释机制,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。
注解可分为三类:
- Java 自带的标准注解
- 元注解
- 自定义注解
二、Java 自带的标准注解(内置注解)
Java 自带的标准注解,也叫内置注解,在 java.lang 包下(随着 JDK 版本的升级,Java 开发者们可能会在之后更高的版本中引入更多的内置注解),包括 @Override、@Deprecated、@SuppressWarnings、@FunctionalInterface、@SafeVarargs 等,使用这些注解后编译器就会进行检查。
各个注解的作用如下。
- @Override
检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误 - @Deprecated
标记过时方法。如果使用该方法,会报编译警告,但是不影响使用 - @SuppressWarnings
指示编译器去忽略注解中声明的警告 - @FunctionalInterface
Java 8 开始支持,标识一个匿名函数或函数式接口 - @SafeVarargs
Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
三、元注解
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。明白点说,就是我们定义注解时用的注解就是元注解。元注解是用于定义注解的注解,在 java.lang.annotation 中。
除了 @ 符号,注解很像是一个接口。
在注解中一般会有一些元素以表示某些值(例如 @SuppressWarnings("all")
括号内的 all)。注解的元素看起来就像接口的方法,唯一的区别在于可以为其制定默认值。没有元素的注解称为标记注解,例如 @Test 就是一个标记注解。
注解的可用类型包括以下几种:所有基本数据类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。元素不能使用 null 作为默认值。注解在只有一个元素且该元素的名称是 value 的情况下,在使用注解的时候可以省略 "value="
,直接写需要的值即可。
元注解一共有 5 种:@Retention、@Documented、@Target、@Inherited、@Repeatable。
3.1 @Retention
用于指定注解的保留策略,标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在运行时可以通过反射访问。它的 value 有以下三个参数。
- RetentionPolicy.SOURCE
注解只保留在源文件中,在编译时会被编译器丢弃 - RetentionPolicy.CLASS
注解保留在 class 文件中,但不会被加载到 JVM(Java 虚拟机)中,运行时无法获得(这是默认策略) - RetentionPolicy.RUNTIME
注解会被保留在 class 文件中,且会被加载到虚拟机中,注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解
3.2 @Documented
用于将注解包含在 javadoc 中。默认情况下,javadoc 是不包括注解的,但如果使用了 @Documented 注解,则相关注解类型信息会被包含在生成的文档中。
3.3 @Target
用于指定注解的使用范围,说明该注解可以被声明在哪些元素之前。它的 value 是 ElementType 集合。ElementType 枚举值如下所示。
- ElementType.TYPE
应用于类、接口(包括注解类型)、枚举 - ElementType.FIELD
应用于字段 - ElementType.METHOD
应用于方法 - ElementType.PARAMETER
应用于方法的参数 - ElementType.CONSTRUCTOR
应用于构造函数 - ElementType.LOCAL_VARIABLE
应用于局部变量 - ElementType.ANNOTATION_TYPE
应用于注解类型 - ElementType.PACKAGE
应用于包 - ElementType.TYPE_PARAMETER(JDK 1.8 新增)
应用于类型变量 - ElementType.TYPE_USE(JDK 1.8 新增)
应用于任何使用类型的语句中
3.4 @Inherited
用于指明父类注解会被子类继承得到。并不是说注解本身可以继承,而是说如果一个父类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了父类的注解。比较拗口,实际效果可以参考下述测试代码。
定义应用于父类的注解:
package com.example.demo.help;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ParentAnnotation {
String value();
}
定义父类:
package com.example.demo.help;
@ParentAnnotation(value = "value")
public class Parent {
}
定义子类,测试输出,效果是子类继承了父类的注解:
package com.example.demo.help;
import java.lang.annotation.Annotation;
import java.util.Arrays;
public class Son extends Parent {
public static void main(String[] args) {
Annotation[] annotations = Son.class.getAnnotations();
// [@com.example.demo.help.ParentAnnotation(value=value)]
System.out.println(Arrays.toString(annotations));
}
}
3.5 @Repeatable
Java 8 开始支持,标识某注解可以在同一个声明上使用多次。测试代码如下。
创建测试注解 TestAnnotation,声明可重复使用:
package com.example.demo.help;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatableAnnotation.class)
public @interface TestAnnotation {
String value();
}
RepeatableAnnotation 注解如下:
package com.example.demo.help;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableAnnotation {
TestAnnotation[] value();
}
创建测试类 TestRepeatable,重复使用注解进行测试:
package com.example.demo.help;
@TestAnnotation(value = "value1")
@TestAnnotation(value = "value2")
public class TestRepeatable {
public static void main(String[] args) {
RepeatableAnnotation annotation = TestRepeatable.class.getAnnotation(RepeatableAnnotation.class);
TestAnnotation[] testAnnotations = annotation.value();
for (TestAnnotation testAnnotation : testAnnotations) {
// @com.example.demo.help.TestAnnotation(value=value1)
// @com.example.demo.help.TestAnnotation(value=value2)
System.out.println(testAnnotation);
}
}
}
四、自定义注解
Java 还可以让使用者根据自己的需求自定义注解,实现个性化处理,使用示例详见:《SpringBoot 自定义注解 + AOP 实现必填参数非空校验、接口传入参数和应答数据打印、方法耗时统计》。
五、Java 注解有什么用?
注解是一个辅助类,它在 Junit、Spring 等工具框架中被广泛使用。我们在编程中经常会使用到的注解作用有以下几种。
5.1 编译检查
注解具有让编译器进行编译检查的作用,例如 @SuppressWarnings、@Deprecated 和 @Override 都具有编译检查作用。若某个方法被 @Override 标注,则意味着该方法会覆盖父类中的同名方法。如果有方法被 @Override 标示,但父类中却没有同名方法,则编译器会报错。
5.2 在反射中使用 Annotation
在反射的 Class、Method、Field 等函数中,有许多与注解相关的接口。这也意味着,我们可以在反射中解析并使用注解。
5.3 根据注解生成帮助文档
通过给注解加上 @Documented 标签,能使该注解标签出现在 javadoc 中。
5.4 能够帮忙查看代码
通过 @Override、@Deprecated 等,我们能很方便的了解程序的大致结构。另外,我们也可以通过自定义注解来实现一些功能。