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 张龙系列>>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容