本文共 4871 字,大约阅读时间需要 16 分钟。
在Android开发的过程中,我们为了减少重复代码的编写,会使用类似,
这类依赖注解库。代码示例如下://不使用Button btn = (Button)findViewById(R.id.btn);//使用ButterKnife@Bind(R.id.btn)Button btn;//使用AndroidAnnotations@ViewById(R.id.btn)Button btn;
可以看出通过注解,我们能大量减少啰嗦的声明和强转类型的代码的编写。
通过本篇文章的学习,对注解有个基本的认识和了解。注解(也叫元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。
@符号的使用,其他和Java固有语法一样
Java SE5内置了三种,定义在java.lang中的注解:
@Override 当前的方法定义将覆盖父类(超类)中的方法。
@Deprecated 被注解的元素被取代,不推荐使用() @SuppressWarnings 关闭不当的编译器警告信息
Java提供四种注解,专门负责新注解的创建。这种注解叫做元注解。
public enum ElementType { //类、接口(包括注解类型)或是enum声明 TYPE, //域声明(包括enum实例) FIELD, //方法声明 METHOD, //参数声明 PARAMETER, //构造器声明 CONSTRUCTOR, //局部变量声明 LOCAL_VARIABLE, //注解类型声明 ANNOTATION_TYPE, //包声明 PACKAGE, //类型参数声明(Java1.8开始使用) TYPE_PARAMETER, //类型使用(Java1.8开始使用) TYPE_USE}
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. *注解将被编译器丢弃 */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. *注解被编译器保存在类文件中。但在运行的时候没有被VM保存 */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement * 注解将被编译器保存在类文件中,在运行的时候也会被VM保存。因此可通过反射机制读取注解的信息 */ RUNTIME}
3.@Documented 将此注解包含在Javadoc中
4.@Inherited 允许子类继承父类中的注解
package com;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by JohnTsai on 15/11/7. */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Test {}
注解的定义很像一个空的接口。定义注解时,会需要一些元注解(meta-annotation,如上所示)
在注解中,一般都会包含一些元素以表示某些值。分析处理注解时,程序或工具可以利用这些值。 注解的元素看起来就像接口的方法,唯一的区别是可以为它设置默认值。package com;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by JohnTsai on 15/11/7. */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface UseCase { public int id(); //default设置默认值 public String description() default "no description";}
package com;/** * Created by JohnTsai on 15/11/7. */public class Testable { public void execute(){ System.out.println("Executing..."); } @Test void testExcute(){ execute(); }}
注解的元素在使用时表现为名-值对的形式。
package com;/** * Created by JohnTsai on 15/11/7. */public class PasswordUtils { @UseCase(id = 47, description = "Passwords must cantain at least one numeric!") public boolean validatePassword(String password) { return (password.matches("\\w*\\d\\w*")); } @UseCase(id = 48) public String encryptPassword(String password) { return new StringBuilder(password).reverse().toString(); }}
每当创建描述符性质的类或接口时,如果包含了重复性的工作,就可以考虑使用注解来简化与自动化该过程。
使用注解很重要的一部分就是创建与使用注解处理器。Java SE5拓展了反射机制的API(构造这类工具),还提供了外部工具apt帮助我们解析带有注解的Java源代码。
package com;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;import java.util.List;/** * Created by JohnTsai on 15/11/8. */public class UseCaseTracker { public static void trackUseCases(ListuseCases,Class clz){ for(Method m :clz.getDeclaredMethods()){ UseCase useCase = m.getAnnotation(UseCase.class); if(useCase!=null){ System.out.println(useCase.id()+useCase.description()); useCases.remove(new Integer(useCase.id())); } } for(int i:useCases){ System.out.println("Warning:Missing use case -"+i); } } public static void main(String[] args) { List useCases = new ArrayList<>(); Collections.addAll(useCases,47,48,49); trackUseCases(useCases,PasswordUtils.class); }}//output/*47Passwords must cantain at least one numeric!48no descriptionWarning:Missing use case -49*/
使用了两个反射的方法:getDeclaredMethods()和getAnnotation()(这两个方法都属于AnnotatedElement接口,Class,Method和Field等类都实现了这个接口)
注解元素可用的类型如下:
1.所有的基本类型(如int,float,boolean等等)2.String3.Class4.enum5.Annotation6.以上类型的数组
编译器对元素的默认值有限制。
不能有不确定的值 (即要么具有默认值,要么使用注解时提供的元素的值),查看它的写法,就能看出默认值的写法:@Retention(RetentionPolicy.CLASS)@Target(ElementType.FIELD)public @interface ViewById { int value() default ResId.DEFAULT_VALUE; String resName() default "";}