Java 5之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),例如在方法覆盖中使用过的@Override注解,注解都是@符号开头的。
注解并不能改变程序运行的结果,不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
基本注解
无论是哪一种注解,本质上都一种数据类型,是一种接口类型。到Java 8为止Java SE提供11种内置注解。其中有5是基本注解,它们来自于java.lang包。有6个是元注解 (Meta?Annotation),它们来自于java.lang.annotation包,自定义注解会用到元注解。
@Override
@Override只能用于方法,子类覆盖父类方法(或者实现接口的方法)时可以@Override注解。编译器会检查被@Override注解的方法,确保该方法父类中存在的方法,否则会有编译错误。
如果不使用@Override,那么子类的方法覆盖父类的方法时,子类方法出错也不会报错;而使用了之后错误的子类的方法就会报错。
@Deprecated
@Deprecated用来指示API已经过时了,@Deprecated可以用来注解类、接口、成员方法和成员变量。
就是对于过时的API,使用@Deprecated能够继续编译而不会化横线。
@SuppressWarnings
@SuppressWarnings注解用来抑制编译器警告,如果你确认程序中的警告没有问题,可以不用理会。但是就是不想看到这些警告,可以使用@SuppressWarnings注解消除这些警告。
就是对于那个警告虽然不会报错,但是那个警告如果看的不舒服可以使用@SuppressWarnings来消除警告
@SafeVarargs
当方法的参数个数发生变化的时候,可能因为类型不同而发生警告,而这个时候使用@SafeVarargs就可以抑制编译器警告。
@FunctionalInterface
@FunctionalInterface注解是Java 8增加的,用于接口的注解,声明接口是函数式接口
该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionalInterface,那么编译器会报错
元注解
元注解包括:@Documented、@Target、@Retention、@Inherited、@Repeatable和@Native。元注解是为其他注解进行说明的注解,当自定义一个新的注解类型时,其中可以使用元注解。
- @Documented
如果在一个自定义注解中引用@Documented注解,那么该注解可以修饰代码元素(类、接口、成员变量和成员方法等),javadoc等工具可以提取这些注解信息。
- @Target
@Target注解用来指定一个新注解的适用目标。@Target注解有一个成员(value)用来设置适用目标,value是java.lang.annotation.ElementType枚举类型的数组,ElementType描述Java程序元素类型,它有10个枚举常量。
ElementType枚举类型中的枚举常量
枚举常量 | 说明 |
---|---|
ANNOTATION | 其他注解类声明 |
CONSTRUCTOR | 构造方法声明 |
FIELD | 成员变量或常量声明 |
LOCAL_VARIABLE | 局部变量声明 |
METHOD | 方法声明 |
PACKAGE | 包声明 |
PARAMETER | 参数声明 |
TYPE | 类、接口声明 |
TYPE_PARAMETER | 用于泛型中类型参数声明,Java8推出 |
TYPE_USE | 用于任何类型的声明,Java8推出 |
- @Retention
@Retention注解用来指定一个新注解的有效范围,@Retention注解有一个成员(value)用来设置保留策略,value是java.lang.annotation.RetentionPolicy枚举类型,RetentionPolicy描述注解保留策略,它有3个枚举常量。
枚举常量 | 说明 |
---|---|
SOURCE | 只适用于Java源代码文件中,此范围最小 |
CLASS | 编辑器把注解信息记录在字节码文件中,此范围适中 |
RUNTIME | 编辑器把注解信息记录在字节码文件中,并在运行过程中可以读取这些信息,此范围最大 |
- @Inherited
@Inherited注解用来指定一个新注解可以被继承。假定一个类A被该新注解修饰,那么这个A类的子类会继承该新注解。
- @Repeatable
@Repeatable注解是Java 8新增加的,它允许在相同的程序元素中重复注释,可重复的注释必须使用@Repeatable进行注释。
- @Native
@Native注解一个成员变量,指示这个变量可以被本地代码引用。常常被代码生成工具使用。
自定义注解
自定义注解 就是系统提供的注解不能满足需求,可以自定义注解,注解本质是一种接口,它是java.lang.annotation.Annotation接口的子接口,是引用数据类型。
声明注解
声明自定义注解可以使用@interface关键字实现,,@interface声明一个注解类型,它前面的访问限定修饰符与类一样有两种:公有访问权限和默认访问权限。
注意 关于注解源程序文件与类一样,一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的,源程序文件命名与公有访问权限的注解名一致。
案例:使用元注解
第一个注解MyAnnotation,它用来修饰类或接口
package Annotation;
import java.lang.annotation.*;
//使用@Documented指定MyAnnotation注解信息可以被javadoc工具读取。
@Documented
//@Target({ ElementType.TYPE })指定MyAnnotation注解用于修饰类和接口等类型。
@Target({ElementType.TYPE})
//@Retention(RetentionPolicy.RUNTIME)指定MyAnnotation注解信息可以在运行时被读取。
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//description是MyAnnotation注解的成员。
String description();
}
第二个注解MemberAnnotation,它用来类中成员变量和成员方法
package Annotation;
import java.lang.annotation.*;
//@Retention(RetentionPolicy.RUNTIME)指定MemberAnnotation注解信息可以在运 行时被读取。
@Documented
//行@Target({ ElementType.FIELD, ElementType.METHOD })指定 MemberAnnotation注解用于修饰类中成员。
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface MemberAnnotation {
//声明两个成员,type类型是Class<?>,默认值是void.class,void.class是void类型 表示方式。description类型是String,没有设置默认值。
Class<?> type() default void.class;
String description();
}
案例:读取运行时注解信息
package Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class HelloWorld {
public static void main(String[] args) {
try {
//创建Person类对应的Class对象
Class<?> clz = Class.forName("Annotation.Person");
//是判断Person类是否存在MyAnnotation注解
if (clz.isAnnotationPresent(MyAnnotation.class)){
//getAnnotation方法将MyAnnotation注解实例返回。
MyAnnotation ann = (MyAnnotation)clz.getAnnotation(MyAnnotation.class);
//ann.description()表达式读取MyAnnotation注解中description成员内容。
System.out.printf("类%s,读取注解描述: %s \n", clz.getName(), ann.description());
}
//获得所有成员方法对象数组,通过遍历方法对象数组
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
//判断方法中是否存 在MemberAnnotation注解
if (method.isAnnotationPresent(MemberAnnotation.class)) {
//getAnnotation方法将MemberAnnotation注解实 例返回。
MemberAnnotation ann = method.getAnnotation(MemberAnnotation.class);
//中ann.description()表达式读取MemberAnnotation注解中description成员内容。
System.out.printf("方法%s,读取注解描述: %s \n", method.getName(), ann.description());
}
}
//获得所有成员变量对象数组
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(MemberAnnotation.class)) {
//判断成员变量中是否存在MemberAnnotation 注解
MemberAnnotation ann = field.getAnnotation(MemberAnnotation.class);
System.out.printf("成员变量%s,读取注解描述: %s \n", field.getName(), ann.description());
} }
} catch (Exception e) {
e.printStackTrace();
}
}
}
本章小结
首先介绍了基本注解,接着介绍了元注解,最后介绍了自定义注解。使用注解能够写出很灵活的代码,注解也特别适合做为使用框架的一种方式。
所以学会使用注解还是很有用的,毕竟这对于上手框架或实现自己的框架都是非常重要的知识。