Java注解(下文统称Annotation)是何方神圣?
Java注解(Annotation)又称元数据,是一种代码级别的说明。我们经常见到的如: @Override(重写) 、@Deprecated(已废弃)......等等类似东西,就是Java注解。它是JDK1.5及以后版本引入的一个特性,与类class、接口interface、枚举enum是在同一个层次,它用@interface声明。它的作用是修饰编程元素。什么是编程元素呢?例如:包、类、构造方法、方法、成员变量等。
##为什么使用Annotation?
笔者窃以为有以下几点:
~~- 简洁 ~~
- 逼格
- ...
怎么使用Annotation?
- Annotation语法
1.以@interface关键字定义
2.注解包含成员,成员以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。
3.成员赋值是通过@Annotation(name=value)的形式赋值。
4.注解需要标明注解的生命周期,注解的修饰目标等信息,这些信息是通过元注解实现。
这里需要说明:注解分为两类,一类是元注解,另外一类是普通注解。所谓元注解就是修饰注解的注解。拿到一个注解,如何知道它是否是元注解呢?需要看它的元注解(无论是元注解还是普通注解都是有元注解的),如果看到这样的元注解:@Target(ElementType.ANNOTATION_TYPE),那么此时这个注解一定是元注解。
上面这4点就是Annotation的一般语法,估计很多同学会觉得太“官方”了。得,Talk is cheap,show me the f**king code。请看下面代码:
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int[] value1();
String value2() default "默认string";
}```
上面代码段就是声明Annotation的一般语法,各位先别懵逼,接下来我根据上面代码段一一说明。
1.```@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })```元注解**@Target**,它代表我们当前定义的注解的**作用域**,即可以用来标记哪些编程元素(编程元素例如:包、类、构造方法、方法、成员变量等。)。**@Target**后面括号里面的几个**ElementType**参数就是它具体的作用域,**ElementType**可能取值有:
(1) ElementType.**CONSTRUCTOR**:构造器上使用的注解
(2) ElementType.**TYPE**:类,接口(包括注解)或者enum上使用的注解
(3) ElementType.**FIELD**:在field(成员变量,包括静态非静态)属性,也包括enum常量上使用的注解
(4) ElementType.**METHOD**:在方法声明上使用的注解
(5) ElementType.**PARAMETER**:在参数上使用的注解
(6) ElementType.**LOCAL_VARIABLE**:在局部变量上使用的注解
(7) ElementType.**ANNOTATION_TYPE**:在注解上使用的**元注解**
(8) ElementType.**PACKAGE**:在包上使用的注解
根据上面讲解,我们现在可以知道```@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })```就规定了我们定义的TestAnnotation只能标记在类|接口|enum|成员变量|方法上使用该注解。如果将该注解标记在其他未声明ElementType上,会报下图错。
![非法作用域.png](http://upload-images.jianshu.io/upload_images/2954781-1aa2a50da07aad9e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
好了,继续讲解下一个!
2.```@Retention(RetentionPolicy.RUNTIME)```注解需要标明注解的生命周期,这些信息是通过元注解**@Retention**实现。因此**@Retention**表示在什么级别保留此信息,相同的,它也有以下几种取值可能(按生命周期由短到长):
(1)RetentionPolicy.**SOURCE**:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
(2)RetentionPolicy.**CLASS**:注解被保留到class文件,jvm加载class文件时候被遗弃。这是**默认的生命周期**(即未声明**@Retention**时默认的生命周期)。
(3)RetentionPolicy.**RUNTIME**:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,保存到class对象中,可以通过反射来获取。
机智的你早已看透我们上面定义的TestAnnotation注解的生命周期了。
3.```public @interface TestAnnotation```,这个不用多说,这是定义一个注解的关键字,类似于定义一个类:```public class HelloWorld```。
4.最后看注解里面的```int[] value1();
String value2() default "默认string";```,结合上文
>注解包含成员,成员以无参数的方法的形式被声明
我们应该明白这个语法,我们看到里面value2后面多了个default,default是默认值,表示如果你在使用该注解时,可以不手动为该成员value赋值,它会默认取default后面的默认值,其他不接default的成员value,则必须手动为其赋值。关于Annotation的基本语法到这里大概结束,下面说一下其他东西。
> 一些细节:当成员value是个数组,用{}形式赋值,如:```@Target(value = { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})```;如果成员名称是value,在赋值过程中可以简写(注意,非名为value的不能简写)如:```@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})```;如果成员类型为数组,但是只赋值一个元素,则也可以简写如:```@Target(ElementType.FIELD)```
- 举个栗子
下面我们一起实现一个~~并没有什么卵用的~~注解栗子,来巩固一下注解的用法,后面我会用1-2篇文章来对注解做进阶的实战。我们先来实现获取注解的value值,然后打印出来。
首先我们定义一个名为TestAnnotation注解
```java
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})//声明作用域
@Retention(RetentionPolicy.RUNTIME)//声明周期
public @interface TestAnnotation {
int[] value1();
String value2() default "默认string";
}
然后我们写一个名为AnnotatedTarget类用来标记上面我们定义的注解
@TestAnnotation(value1 = {1,3,5,7},value2 = "类上String类型的注解参数")
public class AnnotatedTarget {
@TestAnnotation(value1 = {15,16},value2 = "Filed1上String类型的注解参数")
public String filed1;
@TestAnnotation(value1 = {17,18},value2 = "Filed2上String类型的注解参数")
public String filed2;
@TestAnnotation(value1 = {19,20},value2 = "私有Filed3上String类型的注解参数")
private String filed3;
@TestAnnotation(value1 = 2)
public static final int VALUE_FINAL = 2;
@TestAnnotation(value1 = 88)
public void method1 (){
//no-op
}
@TestAnnotation(value1 = 88,value2 = "method2()私有方法注解String")
private void method2 (){
//no-op
}
}
我们已经成功将我们定义的注解TestAnnotation标记在被标记类AnnotatedTarget上面了,相信看过上面那么详细(啰嗦)的讲解后稍微看一下就可以看明白了。接下来,我们如何将这些注解“扫描”出来,并且取出里面所有成员value的值并打印出来呢?这是我们需要编写一个处理这些注解的处理类,TestAnnotationProcessor,里面通过反射,将传入的类里面的TestAnnotation注解“扫描”出来。
/**
* TestAnnotation注解的处理器
*/
public class TestAnnotationProcessor {
public static void process(Class clazz) { //类上注解
Annotation clazzAnnotation = clazz.getAnnotation(TestAnnotation.class);
if (null != clazzAnnotation) {
Log.e("TestAnnotationProcessor", "类上的TestAnnotation参数值:value1 = " + Arrays.toString(((TestAnnotation) clazzAnnotation).value1())+ " , value2 = " + ((TestAnnotation) clazzAnnotation).value2());
}
//方法上注解
for (Method method : clazz.getDeclaredMethods()) {
TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);
if (annotation != null) {
Log.e("TestAnnotationProcessor", "方法上的TestAnnotation参数值:value1 = " + Arrays.toString(((TestAnnotation) annotation).value1()) + " , value2 = " + ((TestAnnotation) annotation).value2());
}
}
//field上注解
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
if (annotation != null) {
Log.e("TestAnnotationProcessor", "field上的TestAnnotation参数值:value1 = "+ Arrays.toString(annotation.value1()) + " , value2 = " + annotation.value2());
}
}
}
}
最后在MainActivity里面测试
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//.....
TestAnnotationProcessor.process(AnnotatedTarget.class);
}
}
运行,可以看到打印出了下面的log:
最后请思考一下,如果我们把@Retention(RetentionPolicy.RUNTIME)生命周期改为另外两个,会有什么结果?为什么?
别看这里,结果是没有打印任何东西
结束
到此关于Annotation基础篇结束,希望各位同学能有所收获,接下来会写两篇关于Annotation实战篇,敬请期待。也希望能指出文中写的不好或者不对的地方,欢迎转载但请尊重笔者劳动,转载请保留博文出处。谢谢大家。