JavaSE 注解——Annotation

1. 学习背景

因为现在很多框架都几乎用到了注解,而使用传统xml进行配置的情况已经逐渐成为了过去,但是仅仅会使用注解是不够的,还需要掌握其原理,这样我们在使用的时候才会自信

2. Annotation的由来

从java5.0版本发布以来,5.0平台提供了一个正式的annotation功能;允许开发者定义、使用自己的annotation类型。此功能由一个定义annotation类型的语法和一个描述annotation声明的语法,读取annotation的API,一个使用annotation修饰的class文件,一个annotation处理工具(apt annotation processing tool)组成

3. Annotation的作用

annotation并不直接影响代码语义,但是它能够工作的方式被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响,annotation可以从源文件、class文件或者以在运行时反射的多种方式被读取

4. Annotation入门

我们先学习几个已知的、熟悉的注解,从而进一步深入学习Annotation

4.1. Override注解学习

首先看一段程序,通过程序来学习Annotation,如下代码编译时会报错:

public class OverrideTest {

    @Override
    public String tostring() {  //关键看此处
        return "This is OverrideTest";
    }
    
    public static void main(String[] args) {
        OverrideTest test = new OverrideTest();
        System.out.println(test);
    }
}

编译器提示如下:tostring()方法必须覆盖或实现一个父类方法

Override error.png

总结:Override注解表示子类要重写(override)父类的对应方法。

4.2. Deprecated注解学习

通过一段程序来学习Annotation,被修饰Deprecated注解的方法在调用时编译器的显示效果如下,调用方法doSomething之时会有删除线:

Deprecated注解.png

总结:Deprecated注解表示方法是不建议被使用的

4.3. SuppressWarnings注解学习

先看如下程序,在编译器中的显示效果如下,存在多条黄线(警告):

未使用注解@SuppressWarnings情况.png

编译器的具体提示如下:

Warning.png

使用@SuppressWarnings后的情况:

使用SuppressWarnings后的情况.png

发现当调用map的相关方法时,警告被抑制住了,(map.put方法上没有黄色线条了)

总结:SuppressWarnings注解表示抑制警告

5. Annotation的定义方式

新建一个Annotation,名字填写为AnnotationTest,双击新建的注解后如下图所示:

自定义注解.png

这样我们就自定义了一个注解

6. Annotation的使用方式

新建一个类AnnotationUsage,并使用自定义注解AnnotationTest,具体代码如下所示:

自定义注解AnnotationTest的使用.png

以上代码可以总结如下两点:

  1. 当注解中的属性名为value时,在对其赋值时可以不指定属性的名称而直接写上属性值即可,除了value以外的其他值都需要使用name=value这种赋值方式,即明确指定给谁赋值

  2. 当自定义一个注解后(该注解没有任何元注解修饰的情况下,之后会讲),它可以使用在任何地方(类上、成员变量上、方法上、方法形参上、局部变量上)

7. Annotation使用细节

7.1. 注解默认值的定义方式

注解默认值定义方式.png

7.2. 注解默认值的使用方式

自定义注解默认值的使用方式.png

7.3. 注解属性是数组类型

如果自定义注解中的属性类型是数组类型,例如下图所示:

自定义注解属性是数组类型.png

那么在使用的时候语法规则有点不同,如下图所示:

自定义注解属性是数组类型情况下的使用.png

总结:如果自定义注解的属性是数组类型,那么在使用的时候,如果只需要赋予一个值得话,那么就可以直接key=value的方式赋值,当然也可以用花括号的方式,如果想要赋予多个值得话,那么必须用花括号的方式

8. 注解底层实现细节

我们讲当我们自定义一个枚举的时候,枚举是自动继承了java.lang.Enum这个类,那么对于注解来说类似,使用@interface自行定义Annotation形态时实际上是自动继承了java.lang.annotation.Annotation接口,由编译程序自动为您完成其他产生的细节,和枚举是一样的。

如果我们定义了一个接口,并且让该接口继承自Annotation,那么我们所定义的接口仍然还是接口,而不是注解

Annotation本身是接口而不是注解,可以与Enum类比

有一点需要注意:在定义Annotation时是不能继承其他类或实现其他接口的,否则编译通不过

9. 包管理注解

定义Annotation型态时也可以使用包来管理类别,方式类同于类的导入功能:之前的Override、Deprecated、SuppressWarnnings因为都是java.lang包下的所以不用import

10. JDK5内建Annotation

10.1. Retention元注解介绍

Retention元注解是为自定义注解服务的,它来指示编译程序该如何对待我们自定义的注解

Retention注解中仅有一个属性value它的类型是RetentionPolicy类型,而RetentionPolicy是一个枚举类,它拥有三个实例:

  • SOURCE:注解仅存于源文件中
  • CLASS:注解会存在于.class文件中,但在运行期不能被VM获取到 (使用反射获取不到注解)
  • RUNTIME:注解会存在于class文件中,但在运行期能被VM获取到 (能通过反射获取到注解)

缺省情况下(也就是我们自定义的注解没有使用@Retention注解的时候),编译程序会将Annotation信息保留在.class文件中,但不被虚拟机读取,而仅用于编译程序或工具程序运行时提供信息,可以简单的理解为在缺省情况下,我们自定义的注解被@Retention(RetentionPolicy.Class)所修饰

对于SuppressWarnings注解中Retention使用的情况如下图所示:

SuppressWarnings中Retention的情况-2.png

对于上图代码可以有如下总结:

  1. 因为在Retention注解中属性名为value,所以在赋予值的时候可以忽略属性名
  2. 对于SuppressWarnings注解而言仅在编译时期告知编译程序来抑制警告,所以不必将这个信息存储与.class文件中
  3. 注解可以被其它注解修饰

一般而言,我们在自定义注解的时候,通常会用元注解@Retention(RetentionPolicy.RUNTIME)来修饰,这样就可以在运行时用反射来做一些文章了,这样注解的作用才会被最大化

10.2. 如何处理@Retention

想要通过反射来处理注解,有一个前提就是只有当自定义注解被@Retention(RetentionPolicy.RUNTIME)修饰的时候,这才有可能发生

java提供了与注解相关的反射接口java.lang.reflect.AnnotatedElement,它有如下几个方法:

  • public Annotation getAnnotation(Class annotationType);
  • public Annotation[] getAnnotations();
  • public Annotation[] getDeclaredAnnotations();
  • public boolean isAnnotationPresent(Class annotationType);

并且Class、Constructor、Field、Method、Package等类别都实现了AnnotatedElement接口

以下有一个例子用来展现注解的作用:

自定义注解Checked:

自定义注解Checked.java .png

方法中带有注解的类Test:

方法中带有注解的类Test.java - M.png

测试类Client:

测试类Client.java.png

输出结果:

hello world
good day

上述例子就是通过方法上是否存在注解@Checked以及它的value值是否为hello world来决定是否执行该方法

总结:注解配合JDK给我们提供的AnnotatedElement API可以发挥很大作用,SpringMVC就用到了大量注解来代替XML,当然底层还是使用了反射技术

10.3. Target元注解介绍

首先Target注解也是一个元注解(即注解的注解)使用java.lng.annotation.Target可以定义我们自定义注解的使用时机,在定义时要指定java.lang.annotation.ElementType的枚举值之一

缺省情况下,我们自定义的注解可以使用在任何地方(类上、成员变量上、方法上、方法形参上、注解上等)

我们自定义一个注解如下:

自定义注解Passed.png

再定义一个被该注解修饰的类如下:

被注解修饰的类PassedTest.png

编译器给的错误提示如下所示:

错误信息.png

因为该注解的Target的value值为ElementType.Method,所以我们自定义的注解@Passed只能修饰在方法上如下所示:

正确的情况PassedTest.png

如果我们想让自定义的注解修饰在多处那怎么办呢?因为Target中的value属性是用ElementType[]修饰的,这表明我们可以使用数组的形式给value赋值,具体如下图所示:

可多处使用的注解@Passed.png

10.4. Documented元注解介绍

当我们自己开发了一个系统,客户想要我们的javadoc时,如果你想让客户在查阅doc中一些类或方法的时候,相关的注解也能有所体现,那么在我们自定义的注解上需要加上元注解@Documented

例如注解@Passed加上Documented注解如下图所示:

Documented修饰的注解Passed.png

这时生成的javadoc是这样的:

javadoc.png

如果@Passed没有加上@Documented注解,那么生成的javadoc是这样的:

未修饰@Documented的javadoc.png

10.5. Inherited注解介绍

在缺省情况下父类别的Annotation并不会被继承至子类别中,可以在定义Annotation形态时加上java.lang.annotation.Inherited形态的Annotation,这样我们自定义的注解,就可以被继承至子类别中了

现定义了如下注解:

Inherited.png

定义了一个被@InheritAnno修饰的类:

inheritTest.png

定义了一个子类:

Child.png

测试类:

test.png

控制台输出结果如下:

true
true

当@InheritAnno去掉@Inherited时,输出结果如下:

true
false

该demo已经体现出了@Inherited的作用了

另一个例子

自定义一个注解,并没有用@Inherited注解修饰:

InheritAnno.png

自定义一个类,使用了注解@InheritAnno:

inheritTest.png

自定义一个类,继承了InheritTest:

Child.png

测试类:


annotation.j_gaitubao_com_591x321.png

控制台输出结果:

console.png

总结:子类继承父类后,如果父类中的方法有被注解修饰,那么子类继承过来后,该注解也会携带过来,但如果是修饰在Class上的注解,那么不会继承过来,除非修饰的注解有用@Inherited元注解修饰

11. 代码实战

我们都知道junit3.8版本使用方法是定义一个类并继承TestCase,然后该类中的方法以test打头,这样在运行的时候,这些方法就会被自动执行。而Junit 4.X 使用了注解的方式,测试类不需要去继承TestCase,并且待测试的方法不需要以test打头,而是用@Test注解修饰这些待测试的方法即可

他们之间底层的代码差异应该如下:

Junit3.8:

junit3.8.png

Junit4.X:

junit4.x.png

12. 总结

Annotation这块内容还是很重要的,如果有较好的掌握,那么对于之后的框架学习是非常有帮助的

需要记住以下几点:

  • 自定义注解如果想配合反射来使用那么该注解需要修饰@Retention(RetentionPolicy.RUNTIME)
  • 掌握JDK提供的AnnotatedElement接口API下的几个方法:
    • isAnnotationPresent(Class<? extends Annotation> cls)
    • getAnnotation(Class<? extends Annotation> cls)
    • getAnnotations()
  • 掌握Annotation语法
    • Annotation定义关键字@interface
    • Annotation属性value在赋值的时候可以缺省
    • Annotation属性如果是数组类型,那么赋值的时候如果想赋予多个值那么需要使用花括号,如果只赋予一个值那么花括号可用可不用
    • Annotation属性可以定义默认值 关键字为default
  • 掌握元注解的使用:
    • @Retention 重要
    • @Target 重要
    • @Document 了解
    • @Inherited 了解

13. 文章引用来源

<<JavaSE 张龙系列>>

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

推荐阅读更多精彩内容