JAVA注解
1.概念描述
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一种特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等前面,用来对这些元素进行说明、注释。
2.作用分类
(1)编写文档:通过代码里标识的注解生成文档【生成doc文档】
(2)代码分析:通过代码里标识的注解对代码进行分析【使用反射】
(3)编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【如Override】
3.JDK内置注解
(1)@Override
检测被该注解标注的方法是否是继承自父类(接口)的。如toString()方法。
(2)@Deprecated
标识该注解标识的内容已过时。
例如某个类中的一个方法在版本1时有缺陷。在版本2时又定义了相同的方法对其缺陷进行弥补。这时就可以在版本2的旧方法上使用@Deprecated注释以提醒使用者不推荐使用旧方法。在使用旧方法时,方法上会出现一个小横杠。
使用@Deprecated标识方法过时而不直接删除有缺陷的方法原因在于,如果删除有缺陷的方法,则会导致已经使用了版本1的程序在 版本2时期出现不兼容的问题。
(3)@SuppressWarnings
可以将@SuppressWarnings(“all”)放在类上,以压制整个类中出现的警告。
也可以将@SuppressWarnings(“all”)放在某个方法上,以压制方法中出现的警告。
4.自定义注解
4.1 注解格式
Pro.java
/*
元注解1
元注解2
public @interface 注解名称{ }
*/
@Target({ElementType.TYPE})//元注解
@Retention(RetentionPolicy.RUNTIME)//元注解
public @interface Pro {
String className();
String methodName();
}
4.2 注解本质
注解的本质就是一个接口,该接口默认继承Annotation接口;且注解内定义的属性本质上是接口中的抽象方法。
例如4.1中定义的注解Pro.java可以利用反编译的方式查看Pro.java定义。
具体方法:在命令行中,先通过javac Pro.java编译生成字节码文件Pro.class,在通过javap Pro.class反编译生成对应的Pro.java的定义。
4.3 注解属性
(1)属性的返回值类型取值
8种基本数据类型,String,枚举,注解,以上各类型的数组。
(2)定义了属性,在使用时需要给属性赋值
① 如果定义属性时,使用default关键字给属性默认初始化值,则使用注释时,可以不进行属性的赋值。
② 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
③ 数组赋值时,只使用{ }包裹。如果数组中只要一个值,则{ }可以省略。
4.4 元注解
元注解是用于描述注解的注解。
(1)@Target:描述注解能够作用的位置。
@Target的定义如下图,其属性为ElementType[ ] value();
@Target中value()属性的类型为 ElementType枚举类型的数组。如下图所示。主要使用其中三个值:TYPE,METHOD,FIELD。
TYPE:表示由@Target注释的注释可以作用于类上。
METHOD:表示由@Target注释的注释可以作用于方法上。
FIELD:表示由@Target注释的注释可以作用于成员变量上。
(2)@Retention:描述注解被保留的阶段。
此“阶段”对应JAVA反射机制中描述的JAVA程序的三个阶段。
@Retention的定义如下图,其属性为RetentionPolicy value();
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019111517441820.png#div align=center)
@Retention中value()属性的类型为RetentionPolicy枚举类型的数组。如下图所示。主要使用其中三个值:SOURCE,CLASS,RUNTIME。
SOURCE:表示由@Retention注释描述的注释,不会被保留在class字节码文件中,在编译阶段就会被丢弃。
CLASS:表示由@Retention注释描述的注释,会保留在class字节码文件中,但不会被JVM读取到。
RUNTIME:表示由@Retention注释描述的注释,会保留在class字节码文件中,并被JVM读取到。
(3)@Documented:描述注解是否会被抽取到API文档中。
(4)@Inherited:描述注解是否被子类继承。
5.在程序中使用(解析)注解
5.1解析注解步骤
解析注解也就是获取注解中定义的属性值。
(1)获取注解定义位置的对象(Class对象、Method对象、Field对象);
获取注解定义位置的对象本质上是在内存中生成了一个该注解接口的子类实现对象。
public class PorImpl implements Pro{
public String className(){
return "cn.ecarg.annotation.Person";
}
public String methodName(){
return "eat";
}
}
(2)获取指定的注解。getAnnotation(Class);
(3)调用注解中的抽象方法获取配置的属性值。
5.2 反射案例(用注解替代配置文件)
(1)需求
写一个“框架”,在不改变该类任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
(2)实现方法
① 注解
② 反射
(3)步骤
① 定义注解,选择合适的元注解,利用注解的属性定义要创建的类对象和调用的方法。
② 解析注解。(步骤见5.1)
③ 使用反射技术来加载文件进内存;
④ 创建对象;
⑤ 执行方法。
(4)具体实现
Person.java
package cn.ecarg.domain;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat(){
System.out.println("*********正在执行Person类中的eat()方法**********");
}
public void eat(String food){
System.out.println("eat" + food);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Pro.java
package cn.ecarg.annotation;
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 Pro {
String className();
String methodName();
}
ReflectTest.java
package cn.ecarg.annotation;
import java.lang.Exception;
import java.lang.reflect.Method;
@Pro(className = "cn.ecarg.domain.Person",methodName = "eat")//使用Pro注解
public class ReflectTest{
public static void main(String[] args) throws Exception{
//1. 获取该类的字节码文件对象
Class<ReflectTest> reflectTestClass = ReflectTest.class;
//2. 获取上面的注解对象
Pro an = reflectTestClass.getAnnotation(Pro.class);
//3. 调用注解对象中定义的抽象方法,获取返回值
String className = an.className();//返回属性className对应的全限定类名
String methodName = an.methodName();//返回属性methodName对应的方法名
System.out.println("********方法名,类名*********");
System.out.println(className);
System.out.println(methodName);
System.out.println();
//当得到全限定类名和方法名后,其他的反射操作和利用配置文件相同。也即利用注解的方式替代配置文件的方式去加载需要的全限定类名和方法名。
//4.加载该类进内存
Class cls = Class.forName(className);
//5.创建对象
Object obj = cls.newInstance();
//6.获取方法对象
Method method = cls.getMethod(methodName);
//7.执行方法
method.invoke(obj);
}
}
输出: