什么是注解?
对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。
注解的用处:
1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
2、跟踪代码依赖性,运行时动态处理,获取信息,实现替代配置文件功能。比如Dagger 2 依赖注入,未来java 开发,将大量注解配置,具有很大用处;
3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
@Override,代表某方法是重写父类中的方法
@Deprecated,表示被标记的内容已经过时、不建议被使用,如果被使用编译器会报警告,但程序也能正常运行。
@SuppressWarnings,由于内容被@Deprecated标记后,编译器会有警告,如果想忽略警告可以使用@SuppressWarnings
自定义注解::
自定义注解类编写的一些规则:
如何定义一个注解呢?可以先看看这些已有的注解是如何实现的,我们先以@Override
为例:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
可以看到注解是通过@interface
关键字来定义的,和接口的定义类似,但是又多了@Target()
、@Retention()
,这些是java中的元注解,元注解可以理解为内置的基础注解,用来限定、说明自定义注解。除了这两个元注解外,还有三个元注解@Inherited
、@Repeatable
、@Documented
,后边会详细解释这些元注解的作用!
再看看@SuppressWarnings
注解的实现:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
和@Override
相比最大的区别就是注解体中多了String[] value();
,它代表注解的属性!关于注解属性也会在后边详细的介绍!
所以定义注解时,除了使用@interface
还需要考虑元注解、注解属性,一个自定义注解的伪码如下:
@元注解0
@元注解1
@元注解2
public @interface 注解名称 {
类型 attr0();
类型 attr1();
}
元注解:
java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented – 注解是否将包含在JavaDoc中
@Retention – 什么时候使用该注解
@Target – 注解用于什么地方
@Inherited – 是否允许子类继承该注解
1.)@Retention – 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括
● ElementType.CONSTRUCTOR: 用于描述构造器
● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE: 用于描述局部变量
● ElementType.METHOD: 用于描述方法
● ElementType.PACKAGE: 用于描述包
● ElementType.PARAMETER: 用于描述参数
● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明
3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。
4.)@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。
例子:
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target({ElementType.FIELD,ElementType.METHOD})//定义注解的作用目标**作用范围字段、枚举的常量/方法
@Documented//说明该注解将被包含在javadoc中
@Inherited //这个注解将被用于该class 的子类
public @interface FieldMeta {
/**
* 注解属性:下面介绍
* @return
*/
boolean id() default false;
/**
* 注解属性:字段名称,default 为默认
* @return
*/
String name() default "";
}
注解属性
在注解中定义属性和在接口中定义方法的格式类似,例如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String name();
int age() default 18;
String[] favour();
}
这样就给Test注解定义了name、age两个属性,并用default关键字指定age的默认值为18。可以这样使用定义好的注解:
@TestAnnotation(name = "Tom", age = 12, favour = {"music", "sports"})
public class Test {
}
由于age有默认值,可以在使用注解时不指定它的值。由于favour的类型为数组,所以当其有多个值时需要用{}包起来。
如果自定义注解没有属性或者属性有默认值,则使用时可以直接写@TestAnnotation,省略后边的括号。
注解的属性支持的数据类型如下:
基本类型(byte、short、int、float、double、long、char、boolean),不包括其对应的包装类型
String
. Class,即Class>
enum,例如enum staus {A, B, C}
注解,例如Override test();
及上述类型对应的数组
注解相关的语法糖就介绍到这里了,接下来要关注的是当一个类、方法、属性等使用了注解后,如何提取注解上的信息。
注解与反射
要提取注解上的信息,就要用到反射相关的知识了,下面看一个完整的例子,首先定义TestAnnotation注解,可以作用在类、字段、方法声明的地方,并可以在运行时被获取,以及三个属性:
@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String name();
int age() default 18;
String[] favour();
}
在Test的类、字段、方法声明的地方分别使用TestAnnotation注解:
@TestAnnotation(name = "Test", age = 20, favour = {"music", "sports"})
public class Test {
@TestAnnotation(name = "testField", favour = {"reading", "sports"})
private int testField;
@TestAnnotation(name = "testMethod", age = 10, favour = {"dancing", "music"})
public void testMethod() {
}
@TestAnnotation(name = "testMethod1", age = 12, favour = {"music"})
public void testMethod1() {
}
}
通过反射的方式提取注解信息:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resolve();
}
private void resolve() {
// 解析类上的注解
boolean isPresent = Test.class.isAnnotationPresent(TestAnnotation.class);
if (isPresent) {
TestAnnotation annotation = Test.class.getAnnotation(TestAnnotation.class);
showAnnotation(annotation);
}
// 解析字段上的注解
Field[] fields = Test.class.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(TestAnnotation.class)) {
TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
showAnnotation(annotation);
}
}
// 解析方法上的注解
Method[] methods = Test.class.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(TestAnnotation.class)) {
TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);
showAnnotation(annotation);
}
}
}
private void showAnnotation(TestAnnotation annotation) {
Log.e("Annotation", annotation.name() + "#" + annotation.age() + "#" + Arrays.toString(annotation.favour()));
}
}
运行后的效果如下:
其中涉及到了几个关键的方法,Class、Method、Field等类都有这样的方法:
boolean isAnnotationPresent(Class extends Annotation> annotation),用来判断是否使用了某个注解。
public A getAnnotation(Class annotation),获得指定名称的注解对象。
public Annotation[] getAnnotations(),返回对应元素的全部注解。
public Annotation[] getDeclaredAnnotations(),返回直接在对应元素上使用的注解,不包括父类的注解。
boolean isAnnotationPresent(Class extends Annotation> annotation),用来判断是否使用了某个注解。
public A getAnnotation(Class annotation),获得指定名称的注解对象。
public Annotation[] getAnnotations(),返回对应元素的全部注解。
public Annotation[] getDeclaredAnnotations(),返回直接在对应元素上使用的注解,不包括父类的注解。