Java基础回归之注解Annotation【基础篇】

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:

log.png

最后请思考一下,如果我们把@Retention(RetentionPolicy.RUNTIME)生命周期改为另外两个,会有什么结果?为什么?

别看这里,结果是没有打印任何东西

结束

到此关于Annotation基础篇结束,希望各位同学能有所收获,接下来会写两篇关于Annotation实战篇,敬请期待。也希望能指出文中写的不好或者不对的地方,欢迎转载但请尊重笔者劳动,转载请保留博文出处。谢谢大家。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容