java中的注解Annotation
概念:
注解(Annotation):是java中的元数据,类、方法、变量、参数都可以被注解。利用注解可以标记源码以便编译器为源码生成文档和检查代码,也可以让编译器和注解处理器在编译时根据注解自动生成代码,甚至可以保留到运行时以便改变运行时的行为。Annotation是java1.5之后引入的,便于实现自定义注解,代替一些配置文件xml的功能,但相对的耦合性变高了。
使用场景
1,用于编译期的注解:使用APT注解处理器(具体如下)生成java文件。
1,概念:APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。简单来说就是在编译期,通过注解生成.java文件。
2,优点:使用APT的优点就是方便、简单,可以少写很多重复的代码。用过ButterKnife、Dagger、EventBus等注解框架的同学就能感受到,利用这些框架可以少写很多代码,只要写一些注解就可以了。其实,他们不过是通过注解,生成了一些代码。同时不影响性能。
3,参考:https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650242955&idx=1&sn=11040755b0df385c5d1facccfc21e107&chksm=88638ee4bf1407f23bb123e61d01e5a454223ce91c05e99a84ee38e9c6a870757d020b1f9f87&mpshare=1&scene=1&srcid=0927xhjYZK52CW7EQ6W9U8SK&pass_ticket=ZvyqJY4H5OcgHznzemnUnwNqUsoVykci2cTsY3fgX3kvWZqymiszHGEk5SUcsJJO#rd
2,用于运行期的注解:使用反射的方式解析,给相应的类,变量,方法等加入功能。(注意:使用反射相对更耗性能,需要慎用)
3,用于一些常用的框架,便于使用的简便(既可能是编译期的注解也有可能使用的运行期注解)。如:ButterKnife,Retrofit等。
语法规则
1,创建一个注解类:
//使用@interface代替class来修饰注解类
public @interface TestAnnomation {
//使用类似方法的形式来声明变量:age()等效于类当中的age变量
int age();
String name();
}
2,方法的返回值
1,八种基本数据类型(byte,short,int,long,boolean,float,double,char)
2,String类型
3,Class类型
4,enum枚举类型
5,Annotation类型
6,上面5中类型的数组,如Class[]
3,元注解的类型
@Target : 限定注解使用的范围(value对应枚举:ElementType)
ElementType.TYPE : 类、接口(包括注解类型)、枚举的声明
ElementType.FIELD : 成员变量字段(包括枚举常量)的声明
ElementType.METHOD : 方法的声明
ElementType.PARAMETER : 形参的声明
ElementType.CONSTRUCTOR : 构造器的声明
ElementType.LOCAL_VARIABLE : 本地变量的声明
ElementType.ANNOTATION_TYPE : 注解类型的声明
ElementType.PACKAGE : 包的声明
ElementType.TYPE_PARAMETER : 泛型参数的声明(1.8之后引入)
ElementType.TYPE_USE : 泛型的使用(1.8之后引入)
@Retention : 说明注解的生命周期(value对应枚举:RetentionPolicy)
RetentionPolicy.SOURCE : 只保留在源码中,会被编译器丢弃
RetentionPolicy.CLASS : 注解会被编译器记录在class文件中,但不需要被VM保留到运行时,这也是默认的行为
RetentionPolicy.RUNTIME : 注解会被编译器记录在class文件中并被VM保留到运行时,所以可以通过反射获取
@Documented : 被注解的元素包含到javadoc文档中
@Inherited : 表明被修饰的注解类型是自动继承的。具体解释如下:若一个注解类型被Inherited元注解所修饰,则当用户在一个类声明中查询该注解类型时,若发现这个类声明中不包含这个注解类型,则会自动在这个类的父类中查询相应的注解类型,这个过程会被重复,直到该注解类型被找到或是查找完了Object类还未找到。
@Repeatable : 可重复注解(1.8后引入),使得作用的注解可以取多个值
注解的解析
1,编译期注解的解析
编译时注解指的是@Retention的值为CLASS的注解,对于这类注解的解析,我们只需做以下两件事:
1,自定义类继承 AbstractProcessor类;
2,重写其中的 process 函数。
然后编译器在编译时会自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法。因此我们只要做好上面两件事,编译器就会主动去解析我们的编译时注解。最常用的就是利用APT编译期生成java代码。
2,运行期注解的解析
运行时注解指的是@Retention的值为RUNTIME 的注解,这类注解可以使用反射的方式进行解析注解的具体逻辑。使用的类为:java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的方法。(具体见下方的自定义注解的实现。)
java中常见的内置注解
1,@Deprecated:用于声明方法或变量等过时
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
2,@Override : 用于声明该方法为覆写的父类方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
3,@SuppressWarnings : 用于声明代码中的警告问题点
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
//使用如下:
@SuppressWarnings("unchecked"):添加在方法上,取消对方法的检查
4,@SafeVarargs : 参数安全类型注解(1.7后引入),提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
5,@FunctionalInterface : 表示该接口为函数式接口的注解(1.8后引入:函数式接口 (Functional Interface) = 1个具有1个方法的普通接口)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
Android Support Library 中常见的注解
1,@NonNull : 用于非空判断的注解,如果参数为null则提示警告
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
public @interface NonNull {
}
2,@UiThread、@WorkerThread : 用于进行线程检查的注解
//在主线程(UI)线程中
@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
public @interface UiThread {
}
@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
public @interface WorkerThread {
}
3,@IdRes : 表明这个整数代表资源引用
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface IdRes {
}
4,@IntDef、@StringDef : 注解自定义注解来代替枚举
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
}
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface StringDef {
String[] value() default {};
}
自定义注解
1,创建注解类
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface TestAnnomation {
int age() default 10;
String name() default "小明";
}
2,使用反射解析注解
Class,Field,Constructor,Method均实现了接口AnnotatedElement。该接口上提供的两个方法:
1,getAnnotations() (可以获取对象上所有的注解类);
2,getAnnotation(Class<T> annotationClass)(可以获取对象上指定的注解类)
public class Tracker {
private static final String TAG = "Tracker";
public static void init(Class<?> clazz) {
//1,解析类名上的注解
Annotation[] annotations = clazz.getAnnotations(); //获取类上的所有注解
TestAnnomation annotation = clazz.getAnnotation(TestAnnomation.class); //获取类上的指定注解
if (annotation != null) {
Log.d(TAG, "init: 类上的注解 : " + annotation.age() + " == " + annotation.name());
}
//2,解析成员变量上的注解(这里默认成员变量为private)
Field[] declaredFields = clazz.getDeclaredFields();
if (declaredFields != null && declaredFields.length > 0) {
for (int i = 0; i < declaredFields.length; i++) {
Field declaredField = declaredFields[i];
// Annotation[] annotations1 = declaredField.getAnnotations();
TestAnnomation annotation1 = declaredField.getAnnotation(TestAnnomation.class);
if (annotation1 != null) {
String name = declaredField.getName();
Log.d(TAG, "init: 成员变量上的注解 : 变量名 = " + name + " ;; " + annotation1.age() + " == " + annotation1.name());
}
}
}
//3,解析构造方法上的注解(这里暂时默认构造方法非private)
Constructor<?>[] constructors = clazz.getConstructors();
if (constructors != null && constructors.length > 0) {
for (int i = 0; i < constructors.length; i++) {
Constructor<?> constructor = constructors[i];
// Annotation[] annotations1 = constructor.getAnnotations();
TestAnnomation annotation1 = constructor.getAnnotation(TestAnnomation.class);
if (annotation1 != null) {
Log.d(TAG, "init: 构造方法上的注解 : 参数个数 = " + constructor.getParameterTypes().length + " ;; " + annotation1.age() + " == " + annotation1.name());
}
}
}
//4,解析方法上的注解(这里暂时默认方法非private)
Method[] methods = clazz.getMethods();
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
// Annotation[] annotations1 = method.getAnnotations();
TestAnnomation annotation1 = method.getAnnotation(TestAnnomation.class);
if (annotation1 != null) {
Log.d(TAG, "init: 方法上的注解 : 方法名 = " + method.getName() + " ;; " + annotation1.age() + " == " + annotation1.name());
}
}
}
}
}
3,使用注解
@TestAnnomation(age = 20,name = "类名")
public class Test {
@TestAnnomation(age = 30,name = "变量")
private int number;
private String title;
@TestAnnomation(age = 40,name = "构造方法")
public Test(int number, String title) {
this.number = number;
this.title = title;
}
@TestAnnomation(age = 50,name = "方法")
public int getNumber() {
return number;
}
@TestAnnomation()
public void setNumber(int number) {
this.number = number;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
//需要在使用的地方调用方法:Tracker.init(Test.class);
4,测试结果
09-28 11:30:17.875 11448-11448/com.learn.study D/Tracker: init: 类上的注解 : 20 == 类名
init: 成员变量上的注解 : 变量名 = number ;; 30 == 变量
09-28 11:30:17.876 11448-11448/com.learn.study D/Tracker: init: 构造方法上的注解 : 参数个数 = 2 ;; 40 == 构造方法
init: 方法上的注解 : 方法名 = getNumber ;; 50 == 方法
09-28 11:30:17.877 11448-11448/com.learn.study D/Tracker: init: 方法上的注解 : 方法名 = setNumber ;; 10 == 小明