前言
jdk1.5
引入了注解机制
(Annotation
),用于对java里面的元素(如:Class、Method、Field等等)进行标记。同时,java的反射类库也加入了对Annotation
的支持,因此我们可以利用反射来对特殊的Annotation
进行特殊的处理,增强代码的语义。
本文主要是对Annotation的语法
和Annotation的用法
进行分析阐述。然后对一些java自带的、常用的Annotation
进行说明,加深读者的理解。
整体结构
借用网上的一张图,来说明整体结构。
Annotation整体结构图通过这张图我们看到下面的信息
- 一个
Annotation
是一个接口 - 一个
Annotation
和一个RetentionPolicy
关联 - 一个
Annotation
和多个ElementType
关联 -
Annotation
可以有很多实现,包括java自带的@Override
、@Deprecated
和@Inherited
等等,用户也可以自己定义
为了更好地理解Annotation
,我们将Annotation整体结构
拆分为左右两个部分来讲解。
Annotation的组成
先来看看整体结构的左边部分
整体结构左边部分Annotation
关联了3个重要的类,分别为Annotation
、ElementType
和RetentionPolicy
,这3个类所在的包为java.lang.annotation
,下面我们来看看java里面的定义。
1. Annotation接口
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
2. ElementType枚举
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, // 类、接口(包括annotation类型)或者枚举声明
/** Field declaration (includes enum constants) */
FIELD, // 字段声明(包括枚举常量)
/** Method declaration */
METHOD, // 方法声明
/** Formal parameter declaration */
PARAMETER, // 参数声明
/** Constructor declaration */
CONSTRUCTOR, // 构造器声明
/** Local variable declaration */
LOCAL_VARIABLE, // 本地变量(也叫临时变量)声明
/** Annotation type declaration */
ANNOTATION_TYPE, // annotation类型声明
/** Package declaration */
PACKAGE, // 包声明
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER, // 类型参数声明(泛型的类型参数)
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
3. RetentionPolicy枚举
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE, // 仅用于编译期间,编译完成之后,该annotation就无用了
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS, // 编译器将该annotation编译进class里面,但vm运行期间不会保留,默认行为
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME // 编译器将该annotation编译近class,同时vm运行期间也可以对此annotation使用反射读取
}
接下来我们对这3个类做一下总结
-
Annotation
是一个接口,一个Annotation
包含一个RetentionPolicy
和多个ElementType
-
ElementType
是类型属性,一种类型可以简单地看成一种用途,每个Annotation
可以有多种用途 -
RetentionPolicy
是策略属性,共有3种策略,每个Annotation
可选择一种策略,默认为CLASS
策略-
SOURCE
,仅用于编译期间,编译完成之后,该Annotation
就无用了。@Override
就属于这种策略,仅仅在编译期检查子类是否可覆盖父类 -
CLASS
,编译期将该Annotation
编译进class里面,但vm运行期间不会保留,默认行为 -
RUNTIME
,编译期将该Annotation
编译进class,同时vm运行期间也可以对此Annotation
使用反射读取
-
通用定义
我们来看看Annotation
的通用定义,先来写一个自定义Annotation
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
-
@interface
-- 该修饰符是在关键字interface
前加了一个@
表示这是一个Annotation
-
@Documented
-- 表示该Annotation
可以在javadoc
中出现,默认不会在javadoc
出现 -
@Target(ElementType.TYPE)
-- 表示该Annotation
可以出现在类型声明上面,比如类、接口、枚举等等 -
@Retention(RetentionPolicy.RUNTIME)
-- 表示该Annotation
可以被编译进class,同时在运行期vm也可以通过反射读取到它 -
Annotation
可以声明成员变量,需要注意以下几点- 声明的方式为无参方法,且没有实现体,如:
{type} methodName();
- 方法的返回类型为成员变量类型
- 方法名为成员变量名字
- 成员变量类型只能是
基本类型
、String
和自定义枚举
,其他的Interface、Class等都不能当成成员变量 - 成员变量可以使用
default
来设置默认值,使用时可以不传入值,如:{type} methodName() default {defaultValue};
- 声明的方式为无参方法,且没有实现体,如:
常用Annotation
我们再来看整体结构的右边部分,我们看到很多我们经常接触到的Annotation
。我们会逐一来分析一下。
【元注解】:网上有
元注解
一说,其实注解上面的注解叫做元注解
。当我们通过@Target(ElementType.ANNOTATION_TYPE)
来修饰一个Annotation
的时候,就表示该Annotation
是一个元注解。
在jdk里面,有一些Annotation
是经常用到的,为了加深我们对Annotation
的理解,我们需要对这些Annotation
进行分析。
-
@Documented
,所标注内容,可以出现在javadoc
中。 -
@Inherited
,只能被用来标注Annotation类型
,它所标注的Annotation
具有继承性。 -
@Retention
,只能被用来标注Annotation类型
,而且它被用来指定Annotation
的RetentionPolicy
属性。 -
@Target
,只能被用来标注Annotation类型
,而且它被用来指定Annotation
的ElementType
属性。 -
@Deprecated
,所标注内容,不再被建议使用。 -
@Override
,只能标注方法,表示该方法覆盖父类中的方法。 -
@SuppressWarnings
,所标注内容产生的警告,编译器会对这些警告保持静默。
1. @Inherited
我们写一个例子就能很容易看出@Inherited
的作用了。
public class InheritedTest {
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation {
}
@MyAnnotation class Father {
}
class Child extends Father {
}
public static void main(String[] args) {
MyAnnotation[] annotations = Child.class.getAnnotationsByType(MyAnnotation.class);
System.out.println("annotations length: " + annotations.length);
for (MyAnnotation annotation : annotations) {
System.out.println(annotation);
}
}
}
当Annotation
用@Inherited
修饰时,运行结果如下:
当Annotation
不用@Inherited
修饰时,运行结果如下:
2. @Retention
-
RetentionPolicy.SOURCE
类型的class结构和运行效果
-
RetentionPolicy.CLASS
类型的class结构和运行效果
class结构图
-
RetentionPolicy.RUNTIME
类型的class结构和运行效果
class结构图
3. @Target
ElementType.TYPE
修饰的Annotation
不能作用于字段和方法,在IntellijIdea里面直接就有错误提示,同时编译的时候也会出错。
4. @Deprecated
使用@Deprecated
修饰的类或方法,编译不会出错,运行也不会出错,但是会给出警告。
5. @Override
IDEA警告 编译出错Child.sayHello2()
虽然会有IDEA警告,但是不会编译出错;Child.sayHello3()
因为使用了@Override
修饰,但是在父类里面并没有sayHello3()
这个方法,所以会编译出错;Child.sayHello1()
属于正常使用。
6. @SuppressWarnings
@SuppressWarnings
可用于消除警告,可以消除哪些情况下的警告呢,我们举一个例子。
在这个例子里面
- 我们使用了已经废弃的
java.util.Date
构造方法public Date(int year, int month, int date)
,所以在编译的时候会出现编译警告。 - 因为泛型擦除的原因,往指定泛型插入非泛型类型时会出现警告。
- 通过使用
@SuppressWarnings
,我们可以消除编译期警告
Annotation的作用
我们总结一下Annotation的作用
,大概有下面这几种
- 编译检查,jdk自带的
@SuppressWarnings
、@Deprecated
和@Override
都有编译检查的功能 - 在反射中使用Annotation,这在很多的框架代码里面大量出现,比如Spring、Mybatis、Hibernate等等
- 根据
Annotation
生成帮助文档,通过使用@Documented
,我们可以将Annotation
也生成到javadoc里面 - 能够帮忙查看查看代码,比如通过使用
@Override
和@Deprecated
,我们很容易知道我们的集成层级、废弃状态等等
拾遗1 - @SuppressWarnings可以消除哪些警告
不同的编译器,可以消除的警告会有所不同,如果我们使用的是javac编译器,那么我们可以通过命令javac -X
来列出所有支持的可以消除的警告。常用的有下面这些
-
deprecation
- to suppress warnings relative to deprecation(抑制过期方法警告) -
rawtypes
- to suppress warnings relative to un-specific types when using + generics on class params(使用generics时忽略没有指定相应的类型) -
unchecked
- to suppress warnings relative to unchecked operations(抑制没有进行类型检查操作的警告) -
unused
- to suppress warnings relative to unused code (抑制没被使用过的代码的警告)
拾遗2 - Annotation中的value和default
在Annotation
的成员变量里面,value
很多时候可以简化我们对Annotation
的使用。
- 当我们使用
Annotation
的时候,如果只有一个value
需要传入的时候,我们省略掉value
,即:Element
。 - 当
value
声明为数组,使用时需要用大括号括起来+逗号分隔,如{"FirstElement", "SecondElement"}
- 当
value
声明为数组,使用时只有一个元素时,可当成单元素使用,即:"Element"
public class ValueTest {
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @interface OneElement {
String value();
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @interface MultiElement {
String[] value();
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @interface MultiButOnlyInputOneElement {
String[] value();
}
@OneElement("abc")
@MultiElement({"abc", "cba"})
@MultiButOnlyInputOneElement("abc")
class User {
}
}
同时,在定义Annotation成员变量的时候,我们可以使用default
来设置默认值,使用时如果该成员变量的值和默认值相同,则可省略此成员变量的值传入。
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation {
String value() default "is null";
}
总结
本文,我们首先给出了Annotation的整体结构图
,然后分析了Annotation
的语法和用法,最后我们给出了一些例子来说明了jdk自带的Annotation
的用法,并总结了Annotation
的使用场景。同时,我们通过对一些技巧性使用的补充,加深了我们对Annotation
的印象。
网友评论