java注解
文章目录
1 Java注解是什么?
根据官方文档说明,Java注解
是元数据的一种形式,它不直接影响我们代码本身的操作,但是有以下几个用途:
- 给编译器提供信息-编译器可以用注解来检测错误或者抑制编译时的warning信息
- 编译和部署代码时进一步加工—一些工具可以根据注解生成额外的代码或文件
- 运行时处理:一些注解可以在运行时被检测
2 注解的形式和定义
注解是以@
符号开头,后接注解名称,如果有参数的话,后面再用()
把参数写进去,参数都是key=value
的形式传递,只有一个参数时默认是对应value
这个key,可以不写。
如:
|
|
如何定义一个注解:
注解本质上是interface
的一种形式,只是在interface
关键字前加了个@
符号,如:
|
|
3 Java SE API 预定义的注解
预定义的注解有一些是给编译器使用的,另一些是给其它注解使用的(元注解)。
3.1 编译器用的注解
这些注解的定义本身也有元注解,后面再讲。
@Deprecated:
|
|
表示一个元素(类、方法或属性)被废弃。语言和框架等一直在更新,一些元素因为安全或者性能原因在新版本中被新的类或方法取代,此时旧的方法不能直接删除(为了保持兼容性),但又不建议使用,所以标记为@Deprecated
,当编译器编译时,如果发现你的代码中使用了标记为@Deprecated
的元素,就会生成warning
@Override:
|
|
表示一个方法是重载父类的方法或者实现接口的方法。主要是避免方法名称拼写错误,如果出现了拼写错误,编译器会报错。现在IDE越来越智能,这类错误很少出现了。
@SuppressWarnings:
|
|
让编译器不要显示warning
。比如一个方法调用了另一个标记为deprecated
的方法,正常编译器会生成warning
,用@SuppressWarnings("deprecation")
可以不让其显示。warning
有很多种,比如deprecation
和unchecked
,可以选择同时阻止多个warning
:@SuppressWarnings({"unchecked", "deprecation"})
@SafeVarargs
|
|
1.7引入
作用对象:变长参数的【构造函数或者方法】,方法必须是final
或者static
,java9开始也可以是private
,这个限制主要是为了不让方法被重载。如果参数是固定长度的,或者方法的修饰符不满足上面的要求,编译器都会报错。
效果:(程序员)声明函数体本身不会对变长参数进行潜在的不安全操作,让编译器不生成unchecked warning
。
什么是对变长参数的潜在不安全操作呢?简单说明一下:
查看详细内容
当你的变长参数跟泛型有关(non-reifiable
)的时候,就可能会出现类型转换的安全问题。用官方API的例子来解释:
|
|
这是一个不正确的使用@SafeVarargs
注解的例子,因为这段代码会抛出运行时异常。一行行分析:
首先,方法传入了一个变长参数,形式是List<String>... stringLists
,变长参数在编译时会转化为数组,所以,这个参数在转化后就变成List<String>[] stringLists
,Java不是不支持泛型数组吗???事实上,对于可变参数使用泛型的这种形式是支持的,所以它也会导致那些泛型数组会带来的问题。
Object[] array = stringLists;
然后我们创建了一个Object[]
数组变量array
,并且初始化为stringLists
,这里因为类型擦除,所以在编译后stringLists
指向的类型是List<Object>[]
。因为数组赋值是传递的数组对象的引用,所以array
也是指向的stringLists
的地址,也就是List<Object>[]
,这里是伏笔。
List<Integer> tmpList = Arrays.asList(42);
这一行新建一个List<Integer>
,里面只有一个元素42
array[0] = tmpList;
把上面新建的这个List
存到array[0]
的位置。因为前面的类型擦除,所以这里是把一个List<Integer>
对象放到List<Object>[0]
里面,编译时没有问题,运行到这一步也没有问题。如果我们的参数stringLists
不是泛型,而是普通的类型比如String
,修改例子中的相应代码,那么这一步就会报运行时错误ArrayStoreException
。
String s = stringLists[0].get(0);
右边读取的是Integer
类型的42,想要转化成String
失败,抛出运行时异常,在这一步抛出异常并不清晰,直到使用数组的元素时我们才发现存错了,如果代码复杂一点,会非常难以排查。
概括一下:实际上,“潜在的不安全操作"有很多种情况,大致都是由于数组和泛型的混用引起。@SafeVarargs
可以在方法源头阻止编译器生成unchecked warning
,否则的话需要在每个调用方使用@SuppressWarnings
来抑制这些warning
。使用@SafeVarargs
时要注意几点:
- 只有在你真的确定代码是安全的情况下才使用
@SafeVarargs
,不然会让你的异常更加排查,通常有两个准则需要遵守:- 不直接修改泛型数组变量,只读不改
- 不对外开放泛型数组变量的引用,本质也是同上
- 可以考虑用
List
来替换可变参数,虽然代码会冗长一点,也更安全。
@FunctionalInterface
|
|
1.8引入
表明一个接口是一个函数接口(有且仅有一个抽象方法的接口),如果不满足这个定义,或者这个注解不是被用在接口上,编译器都会报错。
事实上,就算没有这个注解,只要接口满足函数接口的要求,编译器也会把它当成函数接口,所以这个注解提供了一个检测功能。
@Native
|
|
1.8引入
用在一个定义常量的字段上,表明这个字段的值可能引用native code
(比如平台相关的底层库)。一些生成native code
头文件的工具通过这个注解来判断是否需要生成相应的头文件,以及在头文件中应该包含哪些声明。这个注解用的少,通常跟JNI
有关。
3.2 作用在注解上的注解
下面的这些注解只用在其它注解定义时,被称为元注解
@Retention
|
|
表明注解在哪个阶段(含)之前持续存在,接收一个参数value
,类型是RetentionPolicy
,这是一个Enum
类型,包含3个值,如果没有指定value
的值,默认是RetentionPolicy.CLASS
:
RetentionPolicy.SOURCE
:表示注解只存在源代码中,在编译器使用后就抛弃,不写入.class
文件。上面介绍过的@Override
,@SuppressWarnings
都是SOURCE
级别RetentionPolicy.CLASS
:表示注解存在于.class
文件中,但虚拟机执行时会抛弃这些注解RetentionPolicy.RUNTIME
:表示注解一直存在,包括虚拟机执行时。@SafeVarargs
,@FunctonalInterface
都是RUNTIME
级别,主要是为了可以在运行时通过反射获取注解信息
CLASS
和RUNTIME
的区别:
两者都在字节码文件可见,区别是CLASS
会生成一个RuntimeInvisibleAnnotations
,而RUNTIME
会生成RuntimeVisibleAnnotations
,就是注解是否对虚拟机可见,不过这个行为可以被命令行参数覆盖。
通常都直接使用RUNTIME
或者SOURCE
,CLASS
的适用场景并不多
@Documented
|
|
当在一个注解上标记@Documented
时,表示所有使用这个注解的对象在生成javadoc时都会显示这个注解,默认情况javadoc生成时是不包含注解的。如果一个对象有多个注解,那么生成javadoc时是否显示这些注解是根据每一个注解自身是否使用了@Documented
分别决定的。
@Target
|
|
表示注解可以作用的对象。接收一个ElementType[]
,ElementType
是一个Enum
类型,可以有如下取值(基于jdk11):
ElementType.TYPE
:所有class,inteface,enum
类型的声明,其中inteface
也包括了注解ElementType.FIELD
:字段的声明,包括Enum
里面的常量ElementType.METHOD
:方法的声明ElementType.PARAMETER
:形参的声明ElementType.CONSTRUCTOR
:构造函数的声明ElementType.LOCAL_VARIABLE
:本地变量的声明ElementType.ANNOTATION_TYPE
:注解的声明ElementType.PACKAGE
:package的声明ElementType.TYPE_PARAMETER
:1.8引入,类型参数的声明,如public <@MyAnnotation T> T retT(T t){ return t; }
ElementType.TYPE_USE
:1.8引入,TYPE
可以用的地方它都可以用。在1.8之前,注解只能用在“声明”的时候,有了TYPE_USE
,可以在任何跟Type
有关的地方使用它。官方提供了以下几个例子:
|
|
ElementType.MODULE
:1.8引入,module的声明
@Inherited
|
|
如果一个注解被标记为@Inherited
,那么使用了这个注解的类在被继承的时候,类上的注解也会一起被“继承”。这里注解的“继承”表现为:在子类上可以通过反射获取到自身没有但父类有的注解。
一个简单的例子:
|
|
注意:@Inherited
只在class
上生效,interface
无效。
@Repeatable
|
|
1.8引入。
表示一个注解可以在同一个地方被使用多次。接收一个参数Class<? extends Annotation> value()
,这是一个注解类型的Class
,要解释这个,先从没有@Repeatable
的版本说起。
在1.7版本,同一个注解不能在一个地方出现两次,为了实现类似功能,需要用另一个注解的数组参数把两个相同的注解“封装”起来。比如:
|
|
1.8以后上面的代码可以写成:
|
|
对比可以发现,依然需要3个步骤,不同的是@Schedule
定义的时候多了一个@Repeatable(Schedules.class)
注解,这一步是指明我们用了哪一个注解来包装我们的@Schedule
,这里是@Schedules
,所以传入Schedules.class
,然后我们就可以多次使用@Schedule
了,看上去repeatable。
实际上,这只是语法糖,通过编译后的class文件我们可以发现,两个版本都有如下相同内容:
|
|
这个形式跟我们在1.7版本没有引入@Repeatable
之前是一样的
4 关于自定义注解
前面介绍的JAVA原生支持的注解,直接使用就有效果。自定义注解不一样,你直接使用一个自定义注解的时候,编译器和虚拟机无法理解你想表达什么,你需要自己写"注解解析器”,根据注解信息执行相应操作,而注解信息则通过反射获取,所以自定义注解通常要标记为@Retention(RetentionPolicy.RUNTIME)
。Spring
等框架就大量使用了自定义注解。
版权声明 本博客使用CC BY-NC-SA 4.0许可协议(创意共享4.0:保留署名-非商业性使用-相同方式共享)。