Java注解入门
注解笔记
一、注解初接触
概念:JDK1.5之后引入的新特性,是对程序的类、方法、局部变量等等元素的说明【给计算机看的】
使用方式:在相应的[类、方法、变量...]上添加“@注解名”
分类:
1. 编写文档:通过代码中标识的元数据生成文档【doc文档】 2. 代码分析:通过代码中标识的元数据对代码进行分析【使用反射】 3. 编译检查:通过代码中标识的元数据让编译器能够实现基本的编译检查【如@Test、@Override...】
二、预定义的注解
@Override
检验被该注解标注的方法是否是正确有效的覆盖重写- 建议添加,当然如果是正确的覆盖重写不添加也不是错误
- 如果不是正确的覆盖重写会编译报错
@Deprecated
该注解标注的内容,已过时- 比如有更好的方法可以替代该方法,该方法已经过时,不建议使用【当然还可以用】
@SuppressWarning
压制警告- 有一些无伤大雅的黄色警告如果看着不舒服可以通过这种方式让它暂时消除掉
- 通常标注在类上面,并设置参数为“all”
@SuppressWarnings("all")
,会压制该类所有的黄色警告
三、自定义注解
通过查看JDK预定义的注解,我们可以初步了解到一个注解主要是由两部分组成的。一部分是红框内的主体
public @interface 注解名{ 属性列表 }
另一部分是红框外的【这部分待会再看】。并且他们都默认继承了Annotation
接口进一步我们看下红框里的内容。不难推断出注解其本质是一个接口,默认继承了【java.lang.annotation.Annotation】Annotation接口 接口中可以定义的内容有
- 常量
- 成员方法
而我们把注解中定义的抽象方法称作“属性”
注解的属性
定义要求:属性的返回值类型下列取值 【没有void,其他的也不行】
- 基本数据类型
- String
- 枚举
- 注解
- 以及以上类型的数组
注解属性的使用
- 因为其在使用时,需要为其赋值 属性名 = xxx
- 如果在定义属性时为其添加
default
关键字并初始化值,则使用注解时,可以不为该属性赋值 - 如果该注解只有一个属性需要赋值,且其名称为
value
,则使用注解时,可以不写 属性名 = xxx 直接写其值即可
属性赋值方式
- 自定义注解使用时为其属性赋值
- 属性名 = 值
- 枚举类型赋值方式
- 属性名 = Person.person1
- 注解类型赋值方式: 属性名 = @MyAnnotation1
- 数组类型赋值方式: 属性名 = {Person.person1, Person.person2}
- 数组类型赋值多个值用大括号{}包裹,里面写值,用逗号隔
- 如果数组只赋值一个值,则大括号{}可以省略不写 属性名 = Person.person1
- 数组类型赋值多个值用大括号{}包裹,里面写值,用逗号隔
- 自定义注解使用时为其属性赋值
演示
//自定义的注解 public @interface MyAnnotation { public abstract String show1(); int show2(); //下面这个属性可以在使用注解时不必赋值,default默认为34.888 double show3()default 34.888; //枚举类型 Person Person show4(); //注解类型 MyAnnotation1 MyAnnotation1 show5(); //以上类型数组类型 Person[] show6();} //其中枚举类Person public enum Person { //person person1, person2;} //在使用自定义注解时,为其赋值 @MyAnnotation(show1 = "字符串属性赋值", show2 = 34, show4 = Person.person1, show5 = @MyAnnotation1, show6 = {Person.person1, Person.person2}) public void useMyAnnotation() { System.out.println("该方法使用自定义注解标注"); }
四、元注解
概念
上节我们看到一个注解主要有两部分组成,还看下这张图
红框内的内容已经看过了,接下来我们看下红框上面的黄色框中内容。这部分就是元注解,元注解是对注解的标注【也就是注解的注解】,所以元注解也是注解。Java已经帮哦们定义好了,在我们将来自定义注解时可以添加元注解来描述我们自定义的注解
分类【需要掌握】
@Target
:描述注解能够作用的位置【类、方法...】查看其源码
// Target.java //可以被doc文档抽取到 @Documented //注解可以保留到RUNTIME期 @Retention(RetentionPolicy.RUNTIME) //注解可以作用于ANNOTATION_TYPE @Target(ElementType.ANNOTATION_TYPE) public @interface Target { //声明一个ElementType类型的数组属性,属性名为Value ElementType[] value(); } // 其属性是枚举类 ElementType.java public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE}
ElementType 常见取值:
TYPE:被该注解描述的注解可以作用于类、接口、枚举声明上 METHOD:被该注解描述的注解可以作用于方法声明上 FIELD:被该注解描述的注解可以作用于成员变量【包括枚举常量】
如图
)
@Retention
:描述注解被保留的阶段【SOURCE、CLASS、RUNTIME】查看其源码
//被该注解标注的注解可以被doc文档抽取出来 @Documented //注解可以保留到RUNTIME期 @Retention(RetentionPolicy.RUNTIME) //作用于ANNOTATION_TYPE类型 @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); } // 其属性是枚举类 RetentionPolicy.java public enum RetentionPolicy { SOURCE, CLASS, RUNTIME}
RetentionPolicy取值:
SOURCE:比如Override 在编译期检查,不是保留到class字节码文件 CLASS:被该注解描述的注解,会被保留到class文件中,但不是被jvm读取到 RUNTIME:被该注解描述的注解,会被保留到class文件中,并被jvm读取到
@Documented
:描述注解是否被doc抽取到API文档中被该注解描述的注解会被抽取到doc文档中
如图
@Inherited
:描述该注解是否被子类继承【添加此注解则子类将自动继承该注解】
五、注解的解析
步骤
- 获取被注解标注的位置的对应对象【Class对象、Method对象...】。即知晓注解标注的位置是类还是方法还是属性等等
- 获取对应的注解对象【使用.getAnnotation()方法获取】
- 调用注解对象的定义的抽象方法【即注解的属性】,获取配置的属性值
上面2-3两步相当于是有一个该注解接口的一个子类实现类覆盖重写了其抽象方法 然后获取对象,调用覆盖重写的方法,获取方法的返回值
具体实现
Pro.java
//自定义的注解 Pro 其有两个属性 className/methodName //自定义的注解可以作用在类上 @Target(ElementType.TYPE) //自定义的注解可以保留到运行期RUNTIME @Retention(RetentionPolicy.RUNTIME) public @interface Pro { public abstract String className(); public abstract String methodName(); }
于是反射那一节最后的“框架”小练习可以这样写
ReflectPractice2.java
//使用注解时进行赋值 类名与方法名 @Pro(className = "com.rapjoee.day01.domain.Cat", methodName = "eat") public class ReflectPractice2 { public static void main(String[] args) throws Exception { //1. 解析注解 // 1.1 使用反射,获取本类的Class对象 Class<ReflectPractice2> classObj = ReflectPractice2.class; //使用Class对象的getAnnotation()方法获取标注在本类上的特定名称的注解对象 Pro proAnnotation = classObj.getAnnotation(Pro.class); //通过注解对象调用注解对象的方法【注解属性】获取注解上赋的值 // 获取类上定义注解属性时赋的类名与方法名值 String className = proAnnotation.className(); String methodName = proAnnotation.methodName(); //上面两步可以理解为: /* 一个类实现了 Pro注解 覆盖重写其属性【抽象方法】,获取值 * public class ProImpl implements Pro { * //覆盖重写Pro注解里的抽象方法 * public abstract String className(){ * return "在 ReflectPractice2 类上赋值的配置信息"; * } * public abstract String methodName(){ * return "在 ReflectPractice2 类上赋值的配置信息"; * } * } * String className = new ProImpl().className(); //className配置信息 * String methodName = new ProImpl().methodName(); //methodName配置信息 */ //获取配置的类的Class对象 Class targetClass = Class.forName(className); //4. 创建已经加载的类的对象 Object o = targetClass.newInstance(); //获取方法对象 Method method = targetClass.getMethod(methodName, String.class); // 【eat方法有一个String参数,这里获取Method对象需要传递String.class】,且其invoke()方法里传递eat方法的参数 method.invoke(o, "Tom"); } }
这里把配置的类名与方法名作为Pro注解的属性标注在了类上面并进行了赋值。对Pro注解进行解析,取出类名与方法名,进而使用反射对取出来的数据进行使用
Cat.java
public class Cat { //petName私有属性 public String petName; /** * eat方法 直接打印一句话 * @param petName 传递宠物名参数 */ public void eat(String petName) { System.out.println("The pet " + petName + " is eating");}}
备注
值得注意的是,注解解析的过程可以理解为一个类实现了Pro注解【接口】,覆盖重写了其属性【抽象方法】,调用抽象方法返回的值就是在使用注解时赋的值,如下:
结果如下