java annotation 必须_详说JAVA的Annotation,面试不再担心

概述

Java提供了一种可以在源文件中嵌入附加信息的功能(从JDK5开始提供的)。这些信息称为注解(annotation),它们不会改变程序的运行,但是这些信息可以在开发和部署时期让很多工具使用。例如:注解可以由源代码生成器、编译器或者部署工具处理。虽然元数据(metadata)这个术语也用来指示这个功能,但是术语“注解”的描述显得更为恰当,并且更为常用。

dcfcb4c7409ec613e3fee9bb28523886.png

牛刀小试

我们编写一个标准的Person类,并且重写Object类的toString()方法:

06105ab4f716a95895c8b506265fb10d.png

此处,@Override就是一个注解。正如概述中所说的,@Override这个注解不会影响程序的运行,只是告诉编译器toString()方法是Person类重写的父类方法,在本例中,指的是Object类的toString()方法。另外,本例中的@Override注解是简单的标记注解(不带形参的注解),下面我们给出的MyAnnotation就是一个带有形参的注解。关于注解,我们需要了解下图的知识:

deafde97750663f66ffde2a3c0aa4334.png

注解的基本知识

注解是通过基于interface的机制创建的,如下图所示:

4b124728aa62de61580e8d442c1ce947.png

上述语句声明了一个名为MyAnnotation的注解。我们需要注意两点:

1)interface关键字前面有一个@,它告诉编译器此处声明了一个注解

2)我们需要注意两个成员str()和val(),所有的注解都仅由方法组成。但是不能提供方法体,而是有JAVA来实现这些方法,这些方法实际上就像成员变量一样。

所有的注解都自动继承了java.lang.annotation.Annotation接口,所以,注解不能包含extends子句。

起初,注解仅用来标注声明。这样使用它时,任何类型的声明都可以有一个与之对应的注解。例如,类、方法、变量、形式参数、enum都可以有注解。甚至注解本身也可以带有注解。在上述所有情况下,注解都位于其声明之前。从JDK8开始,也可以对类型用法(type use)进行注解,如强制类型转换或者方法返回类型。

使用注解后,需要向其成员传递值。例如:我们在一个方法上使用我们自己写的MyAnnotation注解:

91e7466215f95a0051826b7db99ddd20.png

此处,MyAnnotation注解与test()方法链接,注解名前置了@,后面跟成员初始值的括号列表,为了给成员提供值,需要向成员赋值。因此,本例中,“注解示例”字符串赋值给了str成员,整数100赋值给了val成员。给注解成员赋值时只使用成员的名字,后面不跟括号,和给变量赋值一样。

注解的保留策略

我们知道,注解是一种标记,那么,该标记会被保留到什么程度?保留策略就是用来决定在什么位置丢弃注解。JAVA定义了三种策略,这三种策略被定义在一个枚举类型中:

ec1e7699a0172dfab95d9e1190b8fe8b.png

1)使用SOURCE保留策略的注解,只在源文件中保留,编译期间会被丢弃。

2)使用CLASS保留策略的注解,在编译时被存储到.class文件中,但是在运行时不能通过JVM得到这些注解。

3)使用RUNTIME保留策略的注解,不仅被保留在.class文件中,而且在运行时能够通过JVM得到这些注解。因此,RUNTIME提供了永久的注解。

4)需要注意的是:局部变量声明的注解不能存储在.class文件中

保留策略是通过java的内置注解@Retention指定的,它的一般形式为:@Retention(policy),其中policy必须为RetentionPolicy枚举的常量,注解的默认保留策略是CLASS,下面我们给我们写的MyAnnotation注解指定保留策略:

4db91c1aa4be9efc8ffc3e6be38b7731.png

注解和反射

当一个注解的保留策略是RUNTIME的时候,任何程序都可以在运行的时候通过反射来查询注解信息。关于反射,我们不在此处讨论,我们只需要知道反射可以在运行的时候获取类的相关信息。我们可以用反射包下的AnnotatedElement接口来获取注解的元素相关信息,Constructor、Field、Method、Package和Class都实现了AnnotatedElement接口,因此,我们可以通过反射拿到之前写的test方法(是一个Method类型的对象),然后获取test方法上的注解信息:

0fce332d1f5963e2990527b736d210b3.png

注解成员的默认值

可以为注解成员提供默认值,应用注解时,如果没有为注解成员指定值,就会使用默认值,我们通过使用default子句来添加默认值:

726aa08517bf4108da0236712dcf5721.png

上面的程序为str提供默认值“默认值”,为val提供默认值1024。这样一来,我们在使用@MyAnnotation的时候,可以不需要为这两个成员指定值了,当然也可以指定一个或者两个,因此可以通过以下4种方式使用@MyAnnotation:

9430feaf04b09be7d12ac8112a07c6c5.png

标记注解和单成员注解

标记注解:不包含成员的注解叫做标记注解。

单成员注解:包含一个成员的注解叫做单成员注解。关于单成员注解,我们需要掌握两点:

1)成员名建议统一写成value

2)党成员名是value的时候,应用注解时,可以直接指定值,不需要指定成员的名称。

28482fddef766a8eaacfce8e9ee919d4.png

内置注解

JAVA提供了很多内置注解,大部分都是专用注解,但是有9个使用于一般情况。这9个中,有4个来自java.lang.annotation包,5个来自java.lang包。

1)@Retention

@Retention只能注解其他注解,指定注解的保留策略。

2)@Documented

@Documented只能注解其他注解,告诉工具注解被文档化。

3)@Target

@Target只能注解其他注解,用于指定应用该注解的声明类型。@Target注解只有一个参数,该参数必须是ElementType枚举的常量,ElementType定义的常量如下:

76a36f9e2f257f9966133324f3f1e324.png

在@Target注解中可以指定这些值中的一个或者多个。如果指定多个值的话,必须由大括号{}包围起来:

8e1487fe143d243464b055558aff6fd7.png

4)@Inherited

@Inherited是标记注解,只能用于另一个注解的声明,另外@Inherited注解只影响用于类声明的注解。@Inherited注解会导致父类的直接被子类继承。也就是说,当我们通过反射查询子类注解的时候,如果子类找不到,就查询父类,如果父类中存在并且使用了@Inherited,就返回父类的注解。

5)@Override

@Override是标记注解,只用于方法,说明该方法是重写了父类的方法,这个在牛刀小试的时候就用到了。

6)@Deprecated

@Deprecated是标记注解,指定声明已经过时,被新的声明代替。

7)@FunctionalInterface

@FunctionalInterface是JDK8新增的注解,用于接口,指定该接口是一个函数式接口,函数式接口仅仅包含一个抽象方法,由lambda表达式使用。

8)@SafeVarargs

@SafeVarargs是标记注解,只用于方法和构造方法,指定没有发生于可变长度参数相关的不安全操作。

9)@SuppressWarnings

@SuppressWarnings用于抑制一个或者多个编译器可能报错的警告。

类型注解、参数注解和重复注解

前面我们提到过,注解只能用于声明,JDK8开始,JAVA增加了可以使用注解的地方,在能够使用类型的大多数地方都能够使用注解。例如,可以注解方法的返回值类型,方法内this的类型,强制转换,数组级别,被继承的类以及throws子句。还可以注解泛型,包括泛型类型参数和泛型类型参数。类型注解和参数注解允许工具检查javac本身不进行的额外检查,从而帮助避免错误。

类型注解必须是@Target为ElementType.TYPE_USE的注解,参数注解必须是@Target为ElementType.TYPE_PARAMETER的注解,他们使得注解可以应用在任何地方,例如:

创建类实例:

new @Interned MyObject();

强制类型转换:

myString = (@NonNull String) str;

implements 语句中:

class UnmodifiableList implements @Readonly List { ... }

throw exception声明:

void monitorTemperature() throws @Critical TemperatureException { ... }

需要注意的是,类型注解和参数注解只是语法,并不会影响运行,编译成.class文件不包含类型注解和参数注解。

JDK8还新增了重复注解,被@Repeatable注解的注解叫做重复注解,重复注解允许在同一声明类型(类,属性,或方法)上多次使用同一个注解。实际上,重复注解不是一个语言上的改变,只是编译器层面的改动,技术层面仍然是一样的,我们可以通过其他的技术手段来替代JDK8的重复注解,只是JDK8的重复注解在语义上更加优秀,并且使得代码可读性高。例如,我们想通过注解的方式来指定权限:

JDK8之前的方案(非重复注解方案):

9d05ade0a326e2b367b5cfd3e772a23f.png

JDK8的重复注解方案:

fbc9635087b371f7e29cd5188bf7147f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值