之前有段时间非常想学一下Java的注解,但一直都比较懒,没有完整的学下来。但最近在B站上看到了一个非常好的视频,看完后觉得非常好,因此写下文档作为积累。(视频时黑马程序员的双元课程基础班的视频,具体是谁的并不知道。)
简介
注解(Annotation),也叫元数据,是代码级别的说明(注释)。它是JDK 1.5版本以后引入的一个特性,与类、接口、枚举是同一个层次。它可以生命在包、类、字段、方法、方法参数等的前面,用来对这些元素进行说明、注释。
注解可以理解为标注解释的简写,其作用就像一个标签信息,而单纯的标签并无意义,还需有解析标签的机制,标注+解析,两个操作组合起来,注解的作用就发挥出来了。
注解的作用主要有三点:
1.编写文档:通过代码里标识的元数据生成文档[javadoc文档]
2.代码分析:通过代码里标识的元数据对代码进行分析(使用反射)
3.编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查(eg. Override)
使用注解
内置注解
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。Java 7后新增了三个注解。(2个在java.lang,1个在java.lang.annotation)
作用在代码上的注解有3个(java.lang)中:
代码位置:libcore/ojluni/src/main/java/java/lang
1.@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
2.@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
3.@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
一般传参all,如: @SuppressWarnings("all")
4.@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
5.@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。(lambda表达式必备?)
作用在注解上的注解(Java.lang.annotation),也称为元注解:
代码位置:libcore/ojluni/src/main/java/java/lang/annotation
1.@Retention - 标识这个注解被保留的阶段。
参数取值:RetentionPolicy类型数据
SOURCE:源码阶段,一般给编译器使用,自定义注解很少会使用这个。
CLASS:字节码阶段,但虚拟机不会去加载。(这个是否可被用于字节码插桩中使用?)
RUNTIME:运行时阶段,虚拟机会加载。一般自定义注解使用此阶段。
2.@Documented - 标记这些注解是否包含在用户文档中。使用该元注解的注解,可在生成的javadoc上看到自定义注解的描述。
3.Target - 标记这个注解应该是哪种 Java 元素上。
参数取值:ElementType[]
TYPE:注解作用在Java类型上,包括class、interface(含annotation)、枚举类型。
FIELD:注解作用在Java字段上,包括枚举类型的枚举值。
METHOD:注解作用在方法上。
PARAMETER:注解作用在参数上。
CONSTRUCTOR:注解作用在构造方法上。(METHOD是否包括这个?)
LOCAL_VARIABLE:注解作用在局部变量上。
ANNOTATION_TYPE:注解作用在注解类型上。(TYPE是否包含这个)
PACKAGE:注解作用在包上。
TYPE_PARAMETER:注解作用在Java类型的参数上,Java 8引入。
TYPE_USE:Use of a type?Java 8 后引入。
4.@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
说明:使用这个标记的注解,如果某个具有父子类关系的两个类中,父类使用了注解(@Inherited标记的注解),则子类会继承了父类的这个注解。验证方式:1.如下面的参考博客写代码验证。2.注解使用@Documented标注,然后生成父子类的doc进行验证。
参考:@Inherited详解_qq_43390895的博客-CSDN博客@Inherited是一个标识,用来修饰注解如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解注意:接口用上个@Inherited修饰的注解,其实现类不会继承这个注解父类的方法用了@Inherited修饰的注解,子类也不会继承这个注解当用了@Inherited修饰的注解的@Retention是RetentionPolicy.RUNTIME,则增强了继承性,在反...https://blog.csdn.net/qq_43390895/article/details/100175330 5.@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
说明:@Repeatable 注解是用于声明其它类型注解的元注解,来表示这个声明的注解是可重复的。@Repeatable的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解。
自定义注解
上面学完用于描述注解的元注解后,可以看看注解是怎么写的了。以SuppressWarnings注解为例说明,说一下带有参数的自定义注解是如何编写和使用的。
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
注解使用@interface来进行声明定义,在注解的定义上,会使用一些元注解来对我们自定义的注解进行描述。
抽象出来就是:
元注解
public @interface 注解名称() {
返回值 属性名称()
}
注解属性
注解内可添加我们需要的属性,默认使用value进行命名,也可使用其他的名字命名。
注解的属性并不是单纯的字段或方法,在传参时,可将属性看作是一个字段,使用value=“all”这种方式传参,而在声明时,其又像方法,带有括号和返回值。
属性的返回值类型(也是数据类型)有以下取值:
1.基本数据类型
2.String类型
3.枚举
4.注解
5.以上类型的数组。
使用时的注意点:
1.如果定义属性时,使用default关键字给属性默认初始值,则使用注解时可以不进行属性的赋值。
2.如果只有一个属性需要赋值,并且属性的名字为value,则value可以省略,直接赋值。如SuppressWarnings 的Target元注解的赋值。
3.数组赋值时,值使用{}包裹,如果数组中只有一个值,{}可以省略。
注解本质
注解的本质其实就是一个特殊接口,如果Retention为RUNTIME时,虚拟机会为其创建一个匿名的实例。
还是以SuppressWarnings为例,我们将其编译成class后再使用javap反编译,得出以下结果:
D:\Android\Android11\libcore\ojluni\src\main\java\java\lang>javac SuppressWarnings.java
D:\Android\Android11\libcore\ojluni\src\main\java\java\lang>javap SuppressWarnings.class
Compiled from "SuppressWarnings.java"
public interface java.lang.SuppressWarnings extends java.lang.annotation.Annotation {
public abstract java.lang.String[] value();
}
可以看到,虽然我们在定义注解时,并没有继承于任何东西,但实际上编译器会将@inerface自动继承了 java.lang.annotation.Annotation这个接口。因此注解的本质就是个接口。
注解的解析
开篇也就说了,单纯的注解是并无意义的,只能说是一个标签用于标记,即便是内置的注解,如果没有IDE或者编译器去解析使用,也就无任何意义。
因此,如果希望注解有意义,则必须要有去解析自定义注解的机制,两者相结合,才能发挥注解的作用。在代码中,一般注解都是使用反射的机制去进行解析处理的。
本章主要是使用一个简单的例子进行说明,以作参考:
import java.lang.annotation.Annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;
import java.lang.reflect.Method;
/**
* Annotation在反射函数中的使用示例
*/
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String[] value() default "unknown";
}
/**
* Person类。它会使用MyAnnotation注解。
*/
class Person {
/**
* empty()方法同时被 "@Deprecated" 和 "@MyAnnotation(value={"a","b"})"所标注
* (01) @Deprecated,意味着empty()方法,不再被建议使用
* (02) @MyAnnotation, 意味着empty() 方法对应的MyAnnotation的value值是默认值"unknown"
*/
@MyAnnotation
@Deprecated
public void empty(){
System.out.println("\nempty");
}
/**
* sombody() 被 @MyAnnotation(value={"girl","boy"}) 所标注,
* @MyAnnotation(value={"girl","boy"}), 意味着MyAnnotation的value值是{"girl","boy"}
*/
@MyAnnotation(value={"girl","boy"})
public void somebody(String name, int age){
System.out.println("\nsomebody: "+name+", "+age);
}
}
public class AnnotationTest {
public static void main(String[] args) throws Exception {
// 新建Person
Person person = new Person();
// 获取Person的Class实例
Class<Person> c = Person.class;
// 获取 somebody() 方法的Method实例
Method mSomebody = c.getMethod("somebody", new Class[]{String.class, int.class});
// 执行该方法
mSomebody.invoke(person, new Object[]{"lily", 18});
iteratorAnnotations(mSomebody);
// 获取 somebody() 方法的Method实例
Method mEmpty = c.getMethod("empty", new Class[]{});
// 执行该方法
mEmpty.invoke(person, new Object[]{});
iteratorAnnotations(mEmpty);
}
public static void iteratorAnnotations(Method method) {
// 判断 somebody() 方法是否包含MyAnnotation注解
if(method.isAnnotationPresent(MyAnnotation.class)){
// 获取该方法的MyAnnotation注解实例
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
// 获取 myAnnotation的值,并打印出来
String[] values = myAnnotation.value(); // 方法对应了注解的属性声明
for (String str:values)
System.out.printf(str+", ");
System.out.println();
}
// 获取方法上的所有注解,并打印出来
Annotation[] annotations = method.getAnnotations();
for(Annotation annotation : annotations){
System.out.println(annotation);
}
}
}
运行结果如下:
somebody: lily, 18
girl, boy,
@com.skywang.annotation.MyAnnotation(value=[girl, boy])
empty
unknown,
@com.skywang.annotation.MyAnnotation(value=[unknown])
@java.lang.Deprecated()
以上代码来自于菜鸟教程。