1、背景
在Android开发中会经常使用到Java注解这个知识点,如:重写父类方法时使用@Override注解、阅读框架源码时常看到@Deprecated注解。
特别现在有很多优秀的Android开源框架都是使用注解,如EventBus、ButterKnife、GreenDao等。 Java注解是Java开发一个很重要的知识点,所以觉得有必要对Java注解这块知识点有一个深入的学习。
2、注解的简介
注解属于Java语言的特性,是在Java5.0引入的新特征 ,位于java.lang.annotation包中 。
注解概念:
注解是用于给Java代码附加元数据,可在编译时或运行时解析并处理这些元数据。Java代码可以是包名、类、方法、成员变量、参数等,且附加的元数据不会影响源代码的执行。
我们也可以这样通俗的理解Java注解:想像Java代码如包名、类、方法、成员变量、参数等都是具有生命,注解就是给代码中某些元素贴上去的一张标签。通俗点来讲,注解如同一张标签。这样理解有助于你快速地理解
3、注解的基本语法
很多开发人员会认为注解的地位不高。其实同 classs 和 interface 一样,注解也属于一种类型。通过 @interface 关键字进行定义。
注解的定义:
定义的格式如下
[@Target]
[@Retention]
[@Documented]
[@Inherited]
public @interface [名称] {
// 元素
}
形式跟接口很类似,不过前面多了一个@符号。
下面的创建了一个名为Test的注解,你可以简单理解为创建了一张名字为Test的标签。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
定义注解时可给注解添加属性,也叫注解的成员变量。注解只有成员变量,没有方法。
注解的成员变量的以“无形参的方法”形式来声明。
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value();
}
还可给注解的属性设定默认值
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value() default "hello";
}
注解的属性可支持数据类型有如下:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
注解的使用:
下面定义注解@DbString, 注解中拥有 fieldName 和 isPrimaryKey 两个属性,并为isPrimaryKey设定了默认值。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface DbString {
String fieldName();
boolean isPrimaryKey() default false;
}
在使用的时候,必须给没有设置默认值的属性赋值。赋值的方式是在注解的括号内以 value=”” 形式进行赋值。
public class Person {
private int id;
@DbString(fieldName = "name")
private String name;
}
public class Person {
@DbInt(fieldName = "id", isPrimaryKey = true)
private int id;
@DbString(fieldName = "name", isPrimaryKey = true)
private String name;
}
内置注解:
JDK5.0加入了下面三个内置注解:
1. @Override:表示当前的方法定义将覆盖父类中的方法
2. @Deprecated:表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告
3. @SuppressWarnings:表示关闭编译器警告信息
元注解:
要想让定义的注解能很好的工作,需要学习元注解的使用。Java5.0还提供了四个元注解。
概念:元注解的作用就是负责注解其他注解。或者说元注解是一种基本注解,但是它能够应用到其它注解上面。
如果难于理解的话,可这样理解:元注解也是一张标签,但它是一张特殊的标签,它的作用就是给其他普通的标签进行解释说明的。
元注解 | 作用 | 取值 | 备注 |
@Target | 指定了注解运用的地方 | 下面给出 | 你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。 |
@Retention | 说明注解的存活时间 | 下面给出 | 我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。 |
@Document | 说明注解是否能被文档化 | 无 | 不常用 |
@Inhrited | 说明注解能否被继承 | 无 | 不常用 |
* ElementType.CONSTRUCTOR 可以给构造方法进行注解
* ElementType.FIELD 可以给属性进行注解
* ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
* ElementType.METHOD 可以给方法进行注解
* ElementType.PACKAGE 可以给一个包进行注解
* ElementType.PARAMETER 可以给一个方法内的参数进行注解
* ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
* ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
4、注解的处理
现在我们已知道如何定义和使用注解,但这是注解还是没有意义的,接下来就是学习如何提取注解,并使用这些注解做一些有用的事情。
我通过用标签来比作注解,前面的内容是讲怎么写注解,然后贴到哪个地方去,而现在我们要做的工作就是在某个时刻拿到这些标签内容。并根据标签的内容做一些处理。
我们可以在两时期对注解进行处理:
1. 编译时处理
2. 运行时处理
注解的处理方式是如何划分的?
回顾一下元注解@Retention,当@Retention的值设定为RetentionPolicy.SOURCE时注解只存在.java源文件中, 但设定为RetentionPolicy.CLASS时注解只存在于.class文件中,所以无法在运行时去获取注解的信息,只能在编译时处理。
设定为RetentionPolicy.RUNTIME注解信息会存在.class文件中,通知单JVM加载.class文件时会把注解也加载到JVM中,所以就可在运行时获取注解的信息
运行时处理:
运行时处理是通过反射机制获取注解。
定义一个@FindView注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FindView {
int value();
}
使用注解
public class MainActivity extends AppCompatActivity {
@FindView(R.id.text)
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
处理注解
public class MainActivity extends AppCompatActivity {
@FindView(R.id.text)
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindView();
}
}
public static void bindView(Activity activity) {
Class classObj = activity.getClass();
Field [] fields = classObj.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (field.isAnnotationPresent(FindView.class)) {
FindView findView = field.getAnnotation(FindView.class);
int resId = findView.value();
View view = activity.findViewById(resId);
field.setAccessible(true);
Class<?> targetType = field.getType();
Class<?> viewType = view.getClass();
if (!targetType.isAssignableFrom(viewType)) {
continue;
}
try {
field.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
运行时处理的缺点:
1:通过反射会影响运行效率
2:如果注解无法保存到运行时的话,是无法使用运行时处理的
编译时处理:
编译时处理需要使用到APT技术,该技术提供了一套编译期的注解处理流程。
在编译期扫描.java文件的注解,并传递到注解处理器,注解处理器可根据注解生成新的.java文件,这些新的.java问和原来的.java一起被javac编译。
这里需要引入注解处理器这个概念,注解处理器是一个在javac编译期处理注解的工具,你可以创建注解处理器并注册,在编译期你创建的处理器以Java代码作为输入,生成文件.java文件作为输出。
注意:注解处理器不能修改已经存在的Java类(即不能向已有的类中添加方法)。只能生成新的Java类。
下面定义注解处理器的四个重要的方法
public class CustomProcessor extends AbstractProcessor {
/**
* 初始化注解处理器
* @param processingEnv APT框架的环境对象,通过该对象可以获取到很多工具类
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
/**
* 配置该注解处理器需要处理的注解类型
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
/**
* 配置该注解处理器支持的最新版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
/**
* 用于处理注解,并生成.java文件
* @param annotations
* @param roundEnv
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
return false;
}
}
优点:
1:不在运行时进行操作,所以对程序的性能不会有什么影响
缺点
1:无法对原来的.java文件进行修改
2:生成额外的.java文件
3:因为是在编译期进行处理注解,所以会对编译速度