注解(Annotation)在JDK1.5之后增加的一个新特性,注解的引入意义很大,有很多非常有名的框架,比如Hibernate、Spring,Android新推出的AAC/Room等框架中都大量使用注解。注解作为程序的元数据嵌入到程序。注解可以被解析工具或编译工具解析,此处注意注解不同于注释(comment)。
当一个接口直接继承java.lang.annotation.Annotation接口时,仍是接口,而并非注解。要想自定义注解类型,只能通过@interface关键字的方式,其实通过该方式会隐含地继承.Annotation接口。
所有与Annotation相关的API摘要如下:
1. Documented
源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }表示拥有该注解的元素可通过javadoc此类的工具进行文档化。该类型应用于注解那些影响客户使用带注释(comment)的元素声明的类型。如果类型声明是用Documented来注解的,这种类型的注解被作为被标注的程序成员的公共API。
例如,上面源码@Retention的定义中有一行@Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。
2. Inherited
源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }@Inherited:表示该注解类型被自动继承,如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。@Inherited对字段,子类对于继承的方法才能通过反射获取注解,但是对于子类重写父类的方法是无法获取父类的注解的。,如果要查看子类中父类的字段,需要从子类中通过Clazz.getSuper()获取到父类,在获取对应的字段及其注解
3. Retention
源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }@Retention:表示该注解类型的注解保留的时长。当注解类型声明中没有@Retention元注解,则默认保留策略为RetentionPolicy.CLASS。关于保留策略(RetentionPolicy)是枚举类型,共定义3种保留策略,如下表:
RetentionPolicy含义SOURCE仅存在Java源文件,经过编译器后便丢弃相应的注解CLASS存在Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注释RUNTIME存在源文件、编译生成的Class字节码文件,以及保留在运行时VM中,可通过反射性地读取注解例如,上面源码@Retention的定义中有一行@Retention(RetentionPolicy.RUNTIME),意思是指当前注解的保留策略为RUNTIME,即存在Java源文件,也存在经过编译器编译后的生成的Class字节码文件,同时在运行时虚拟机(VM)中也保留该注解,可通过反射机制获取当前注解内容。
4. Target
源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }@Target:表示该注解类型的所使用的程序元素类型。当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。关于程序元素(ElementType)是枚举类型,共定义8种程序元素,如下表:
ElementType含义ANNOTATION_TYPE注解类型声明CONSTRUCTOR构造方法声明FIELD字段声明(包括枚举常量)LOCAL_VARIABLE局部变量声明METHOD方法声明PACKAGE包声明PARAMETER参数声明TYPE类、接口(包括注解类型)或枚举声明例如,上面源码@Target的定义中有一行@Target(ElementType.ANNOTATION_TYPE),意思是指当前注解的元素类型是注解类型。
Java提供了多种内建的注解,下面接下几个比较常用的注解:@Override、@Deprecated、@SuppressWarnings这3个注解。
@Override(覆写)源码:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }用途:用于告知编译器,我们需要覆写超类的当前方法。如果某个方法带有该注解但并没有覆写超类相应的方法,则编译器会生成一条错误信息。
注解类型分析:@Override可适用元素为方法,仅仅保留在java源文件中。
@Deprecated(不赞成使用)源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }用途:用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。
注解类型分析: @Deprecated可适合用于除注解类型声明之外的所有元素,保留时长为运行时VM。
@SuppressWarnings(压制警告)源码:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }用于:用于告知编译器忽略特定的警告信息,例在泛型中使用原生数据类型。
注解类型分析: @SuppressWarnings可适合用于除注解类型声明和包名之外的所有元素,仅仅保留在java源文件中。
该注解有方法value(),可支持多个字符串参数,例如:
@SupressWarning(value={"uncheck","deprecation"})前面讲的@Override,@Deprecated都是无需参数的,而压制警告是需要带有参数的,可用参数如下:
参数含义deprecation使用了过时的类或方法时的警告unchecked执行了未检查的转换时的警告fallthrough当Switch程序块进入进入下一个case而没有Break时的警告path在类路径、源文件路径等有不存在路径时的警告serial当可序列化的类缺少serialVersionUID定义时的警告finally任意finally子句不能正常完成时的警告all以上所有情况的警告3.4 对比 3种内建注解的对比:
内建注解TargetRetentionOverrideMETHODSOURCESuppressWarnings除ANNOTATION_TYPE和PACKAGE外的所有SOURCEDeprecated除ANNOTATION_TYPE外的所有RUNTIME接下来,通过反射技术来解析自定义注解@AuthorAnno,关于反射类位于包java.lang.reflect,其中有一个接口AnnotatedElement,该接口定义了注释相关的几个核心方法,如下:
返回值方法解释TgetAnnotation(Class annotationClass)当存在该元素的指定类型注解,则返回相应注释,否则返回nullAnnotation[]getAnnotations()返回此元素上存在的所有注解Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注解。booleanisAnnotationPresent(Class annotationClass)当存在该元素的指定类型注解,则返回true,否则返回false使用注解:
public abstract class Person { @Name() protected String mName; @Age(value = 10) protected int mAge; @Gender(Gender.Type.Female) protected String mGender; @Job (name = "Android程序员", isManager = false) protected String mJob; @Level(1) protected int mLevel; public String getName() { return mName; } public void setName(@NonNull String name) { mName = name; } public @Level(1) int getLevel() { return mLevel; } public void setLevel(@Level(1) int level, @Age int age) { mLevel = level; } public @Age int getAge() { return mAge; } public void setAge(@Age int age) { mAge = age; } @Age public @Gender @Name String getGender(@Age(2) int age) { return mGender; } public @Gender @Name @Job(name = "abcd", isManager = true) abstract String getAllInfo(@Level(10) @Age(30)int level); public void setGender(String gender) { mGender = gender; } public void setJob(String job) { mJob = job; } public @Job(name = "anndroid", isManager = false) String getJob(@Level(10) int level) { return mJob; } @Override public String toString() { return "name=" + mName + " age=" + mAge + " gender=" + mGender; } } public class ChinesePersion extends Person { @Override public String getGender(int age) { return super.getGender(age); } @Override public String getAllInfo(int level) { return null; } }测试注解:
public class TestAnnotation { public static void main(String[] args){ analyzeClass(Person.class); System.out.println("\n"); analyzeClass(ChinesePersion.class); } public static void analyzeClass(Class cla){ System.out.println("##################AnalyzeClass " + cla.getName() + " ######################"); displayFileds(cla); displayMedthods(cla); } private static void displayMedthods(Class cla) { Method[] methods = cla.getMethods(); for(Method method : methods){ Annotation[] methodAnnotations = method.getAnnotations(); Annotation[][] paramAnotations = method.getParameterAnnotations(); if(methodAnnotations.length == 0 && paramAnotations.length == 0){ continue; } System.out.println( "============================Analyze methodName=" + method.getName() + "================"); displayMethodAnotations(method); displayParamAnotations(method); } } //打印字段注解 private static void displayFileds(Class cla) { System.out.println( "====================DisplayFields========================"); //只能获取当前类定义的字段,父类的不能获取,如果要获取父类字段的需要Clazz.getSuper()获取到父类,在获取其对应字段 Field fields[] = cla.getDeclaredFields(); for(Field field : fields){ if(field.isAnnotationPresent(Name.class)){ Name name = field.getAnnotation(Name.class); System.out.println("name=" + name.value()); } if(field.isAnnotationPresent(Age.class)){ Age age = field.getAnnotation(Age.class); System.out.println("age=" + age.value()); } if(field.isAnnotationPresent(Gender.class)){ Gender gender = field.getAnnotation(Gender.class); System.out.println("gender=" + gender.value().toString()); } if(field.isAnnotationPresent(Level.class)){ Level level = field.getAnnotation(Level.class); System.out.println("level=" + level); } if(field.isAnnotationPresent(Job.class)){ Job job = field.getAnnotation(Job.class); System.out.println("job name=" + job.name() + " isManager=" + job.isManager()); } } } //打印参数注解 private static void displayParamAnotations(Method method) { Annotation[][] paramAnotations = method.getParameterAnnotations(); if(paramAnotations != null && paramAnotations.length > 0){ for(Annotation[] annotations : paramAnotations){ if(annotations == null || annotations.length == 0){ continue; } for(Annotation annotation : annotations){ System.out.println("*****Param Annotation=" + annotation); } } } } //打印方法注解,只能获取到继承父类的方法的注解,不能获取到重写父类的方法的注解 private static void displayMethodAnotations(Method method) { Annotation[] anotations = method.getAnnotations(); if(anotations != null && anotations.length > 0){ for(Annotation annotation: anotations){ if(annotation instanceof Job){ Job job = (Job) annotation; System.out.println("-----Method Annotation:job name=" + job.name() + " isManager=" + job.isManager()); } if(annotation instanceof Name){ Name name = (Name) annotation; System.out.println("-----Method Annotation:name=" + name.value()); } if(annotation instanceof Age){ Age age = (Age) annotation; System.out.println("-----Method Annotation:age=" + age.value()); } if(annotation instanceof Gender){ Gender gender = (Gender) annotation; System.out.println("-----Method Annotation:gender=" + gender.value().toString()); } if(annotation instanceof Level){ Level level = (Level) annotation; System.out.println("-----Method Annotation:level=" + level); } } } } }输出:
##################AnalyzeClass com.example.administrator.mannotation.Person ###################### ====================DisplayFields======================== name=none age=10 gender=女 job name=Android程序员 isManager=false level=@com.example.administrator.mannotation.Level(value=1) ============================Analyze methodName=setName================ ============================Analyze methodName=getLevel================ -----Method Annotation:level=@com.example.administrator.mannotation.Level(value=1) ============================Analyze methodName=setLevel================ *****Param Annotation=@com.example.administrator.mannotation.Level(value=1) *****Param Annotation=@com.example.administrator.mannotation.Age(value=0) ============================Analyze methodName=getAge================ -----Method Annotation:age=0 ============================Analyze methodName=setAge================ *****Param Annotation=@com.example.administrator.mannotation.Age(value=0) ============================Analyze methodName=getGender================ -----Method Annotation:age=0 -----Method Annotation:gender=男 -----Method Annotation:name=none *****Param Annotation=@com.example.administrator.mannotation.Age(value=2) ============================Analyze methodName=getAllInfo================ -----Method Annotation:gender=男 -----Method Annotation:name=none -----Method Annotation:job name=abcd isManager=true *****Param Annotation=@com.example.administrator.mannotation.Level(value=10) *****Param Annotation=@com.example.administrator.mannotation.Age(value=30) ============================Analyze methodName=setGender================ ============================Analyze methodName=setJob================ ============================Analyze methodName=getJob================ -----Method Annotation:job name=anndroid isManager=false *****Param Annotation=@com.example.administrator.mannotation.Level(value=10) ============================Analyze methodName=wait================ ============================Analyze methodName=wait================ ============================Analyze methodName=equals================ ##################AnalyzeClass com.example.administrator.mannotation.ChinesePersion ###################### ====================DisplayFields======================== ============================Analyze methodName=getGender================ ============================Analyze methodName=getAllInfo================ ============================Analyze methodName=setName================ ============================Analyze methodName=getLevel================ -----Method Annotation:level=@com.example.administrator.mannotation.Level(value=1) ============================Analyze methodName=setLevel================ *****Param Annotation=@com.example.administrator.mannotation.Level(value=1) *****Param Annotation=@com.example.administrator.mannotation.Age(value=0) ============================Analyze methodName=getAge================ -----Method Annotation:age=0 ============================Analyze methodName=setAge================ *****Param Annotation=@com.example.administrator.mannotation.Age(value=0) ============================Analyze methodName=setGender================ ============================Analyze methodName=setJob================ ============================Analyze methodName=getJob================ -----Method Annotation:job name=anndroid isManager=false *****Param Annotation=@com.example.administrator.mannotation.Level(value=10) ============================Analyze methodName=wait================ ============================Analyze methodName=wait================ ============================Analyze methodName=equals================输出
@java.lang.annotation.Documented() @java.lang.annotation.Inherited() @java.lang.annotation.Target(value=[FIELD, METHOD, ANNOTATION_TYPE, TYPE]) @java.lang.annotation.Retention(value=RUNTIME) @java.lang.annotation.Target(value=[FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE]) @java.lang.annotation.Retention(value=RUNTIME) @java.lang.annotation.Inherited() @com.example.administrator.mannotation.Name(value=none) @com.example.administrator.mannotation.Name(value=none) @com.example.administrator.mannotation.Name(value=none) @com.example.administrator.mannotation.Age(value=0)在Java自带注解中,可以通过@IntRange,@FloatRange限制输入参数,
private int mAge; public void setAge(@IntRange(from = 0, to = 100) int age){ mAge = age; } private double mPrice; public void setAge(@FloatRange(from = 10.0f, to = 100.0f) double price){ mPrice = price; } private void test(){ setAge(-1);//编译报错 setAge(10);//编译通过 setPrice(101);//编译报错 setPrice(100.0);//编译通过 }但是,如果代码如下,则编译正常:因为注解的归根结底只是起注释作用,无法去动态判定变量的值
private int mAge; public void setAge(@IntRange(from = 0, to = 100) int age){ mAge = age; } private void test(){ setAge(calAge());//编译通过 } private int calAge() { return -100; }所以正确的使用姿势是:在方法体内也要做参数判断
private int mAge; public void setAge(@IntRange(from = 0, to = 100) int age){ if(age < 0 || age > 100){ return; } mAge = age; } private void test(){ setAge(calAge());//编译通过 } private int calAge() { return -100; }自定义viewID,为给view.setID()时候用:
<resources> <item name="dyn_btn" type="id"></item> <item name="dyn_view" type="id"></item> </resources> public class IdInjectHelper { //获取所有注解字段对应view public static void inject(Activity activity){ Class cla = activity.getClass(); Field fields[] = cla.getDeclaredFields();//获取到所有的字段 for(Field field : fields){ if(field.isAnnotationPresent(IdInject.class)){//获取指定注解的字段 try { IdInject inject = field.getAnnotation(IdInject.class); View view = activity.findViewById(inject.value()); field.setAccessible(true); field.set(activity, view);//给对应view赋值 } catch (IllegalAccessException e) { e.printStackTrace(); } } } } //动态获取某个id的字段 public static void inject(MainActivity activity, int id) { Class cla = activity.getClass(); Field fields[] = cla.getDeclaredFields(); for(Field field : fields){ if(field.isAnnotationPresent(IdInject.class)){ try { IdInject inject = field.getAnnotation(IdInject.class); if(id == inject.value()){ View view = activity.findViewById(inject.value()); field.setAccessible(true); field.set(activity, view); } } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }