本文共 7274 字,大约阅读时间需要 24 分钟。
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
关联了3个重要的类,分别为Annotation
、ElementType
和RetentionPolicy
,这3个类所在的包为java.lang.annotation
,下面我们来看看java里面的定义。
public interface Annotation { boolean equals(Object obj); int hashCode(); String toString(); Class annotationType();}
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}
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
。我们会逐一来分析一下。
整体结构的右边部分
【元注解】:网上有
元注解
一说,其实注解上面的注解叫做元注解
。当我们通过@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
,所标注内容产生的警告,编译器会对这些警告保持静默。我们写一个例子就能很容易看出@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
修饰时,运行结果如下:
使用@Inherited的效果
当Annotation
不用@Inherited
修饰时,运行结果如下:
不使用@Inherited的效果
RetentionPolicy.SOURCE
类型的class结构和运行效果class结构图
运行效果图
RetentionPolicy.CLASS
类型的class结构和运行效果 class结构图
运行效果图
RetentionPolicy.RUNTIME
类型的class结构和运行效果 class结构图
运行效果图
ElementType.TYPE
修饰的Annotation
不能作用于字段和方法,在IntellijIdea里面直接就有错误提示,同时编译的时候也会出错。
IDEA警告
编译错误
使用@Deprecated
修饰的类或方法,编译不会出错,运行也不会出错,但是会给出警告。
IDEA警告
IDEA警告
编译出错
Child.sayHello2()
虽然会有IDEA警告,但是不会编译出错;Child.sayHello3()
因为使用了@Override
修饰,但是在父类里面并没有sayHello3()
这个方法,所以会编译出错;Child.sayHello1()
属于正常使用。
@SuppressWarnings
可用于消除警告,可以消除哪些情况下的警告呢,我们举一个例子。
例子图
在这个例子里面
java.util.Date
构造方法public Date(int year, int month, int date)
,所以在编译的时候会出现编译警告。@SuppressWarnings
,我们可以消除编译期警告我们总结一下Annotation的作用
,大概有下面这几种
@SuppressWarnings
、@Deprecated
和@Override
都有编译检查的功能Annotation
生成帮助文档,通过使用@Documented
,我们可以将Annotation
也生成到javadoc里面@Override
和@Deprecated
,我们很容易知道我们的集成层级、废弃状态等等不同的编译器,可以消除的警告会有所不同,如果我们使用的是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 (抑制没被使用过的代码的警告)在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
的印象。
我有一个微信公众号,经常会分享一些Java技术相关的干货;如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。
转载地址:http://rwgbi.baihongyu.com/