浅谈 Java 中的注解

文章目录
  1. 1. 简介注解
  2. 2. 注解分类
  3. 3. 自定义注解
  4. 4. 常见示例
  5. 5. 参考文献

人啊,就是要学会装逼,装着装着就牛逼了。

引言部分源自无意间看到的一段话,觉得蛮有意思的。

上次谈到了 Java 中的反射机制,就着公司重构项目即将用到的 Dagger,本期接着谈一谈 Java 中的注解。其实,许多开源库都用到了注解的方式,简化代码以提高开发效率。

简介注解

Java 注解又称 Java 标注,是 Java 语言 5.0 版本开始支持加入源代码的特殊语法元数据。Java 语言中的类、方法、变量、参数和包等都可以被标注。Java 标注和 Javadoc 不同,标注有自反性。在编译器生成类文件时,标注可以被嵌入到字节码中,由 Java 虚拟机执行时获取到标注。

以上来源于维基百科。

注解可以由解析工具或编译工具解析,不同于注释。注意,当一个接口直接继承 java.lang.annotation.Annotation 接口时,仍是接口,而非注解。要想自定义注解类型,只能通过@interface关键字的方式,事实上,通过该方式会隐含地继承 .Annotation 接口。

注解分类

一般分为标准注解和元注解。

标准注解:指 Java 自带的几个注解,常用的包括 Override、Deprecated 和 SuppressWarnings,是重写函数,不鼓励使用(有更好方式、使用有风险或已不再维护),忽略某项 Warning。

元注解:指用来定义注解的注解,包括 Documented、Inherited、Retention 和 Target。

「标准注解」

Java 提供了多种内建的标准注解,下面介绍几个常用的注解,分别为 Override、Deprecated 和 SuppressWarnings,如下:

Override

1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
//
}

@Overrride:告知编译器,需要重写父类的当前方法。若某个方法带有该注解,但没有重写父类相应的方法,则编译器会生成一条错误信息。

@Override 适用元素为方法,仅保留在 Java 源文件中。

Deprecated

1
2
3
4
5
6
@Document
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
//
}

@Deprecated:告知编译器,某一程序元素(比如方法或成员变量)不建议使用。Java 在 Javadoc 中推荐使用该注解,一般提供不推荐该方法的原因及相应的替代方法。

@Deprecated 适用于除注解类型声明之外的所有元素,保留时长为运行时 VM。

SuppressWarnings

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

@SuppressWarings:压制警告,告知编译器忽略特定的警告信息,如在泛型中使用原生数据类型。

@SuppressWarnings 适用于除注解类型声明和包名之外的所有元素,仅保留在 Java 源文件中。

此注解有方法value,支持多个字符串参数,如:

1
@SupressWarnings(value={"uncheck","deprecation"})

前面所述,@Override 和 @Deprecated 都是无需参数的,而 @SuppressWarnings (压制警告)需要带有参数,参数如下:

参数 含义
deprecation 使用了过时的类或方法时的警告
unchecked 执行了未检查的转换时的警告
fallthrough switch 程序块进入下一个 case,没有 break 时的警告
path 局部变量声明
serial 方法声明
finally 包声明
all 参数声明

对比

标准注解 Target Retention
Override METHOD SOURCE
SuppressWarnings 除 ANNOTATION_TYPE 和 PACKAGE 外的所有 SOURCE
Deprecated 除 ANNOTATION_TYPE 外的所有 RUNTIME

「元注解」

元注解类型共 4 种,分别为 Documented、Inherited、Retention 和 Target,从 JDK 1.7 的源码角度说明如下:

Documented

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
//
}

@Documented:表示拥有该注解的元素可以通过 Javadoc 此类的工具文档化。该类型用来注解影响用户使用的,且带注释( comment )的元素声明的类型。若类型声明是用 Documented 注解的,则该类型的注解作为被标注程序成员的公共 API。

比如,上面源码 @Retention 定义中的一行@Documented,意思即是指当前注解的元素会被 Javadoc 工具文档化,而后,在 Java API 文档中可以查看到该注解元素。

Inherited

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
//
}

@Inherited:表示该注解类型被自动继承。若用户在当前类中查询这个元注解类型,并且,当前类的声明中不包含这个元注解类型,则将自动查询到当前类的父类是否存在 Inherited 元注解,该动作将被重复执行,直到找到这个标注类型,或是查询到顶层的父类。

Retention

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}

@Retention:表示该注解类型中注解保留的时长。若注解类型声明中没有 @Retention 元注解,则默认保留政策为 RetentionPolicy.CLASS。其中,保留政策( RetentionPolicy )是枚举类型,共定义 3 种保留政策如下表:

RetentionPolicy 含义
SOURCE 仅存在 Java 源文件,经编译器后便丢弃相应的注解。
CLASS 存在 Java 源文件及编译生成的 Class 字节码文件,运行时 VM 不再保留注释。
RUNTIME 存在 Java 源文件及编译生成的 Class 字节码文件,留在运行时 VM 中,反射性地读取。

比如,上面源码 @Retention 的一行@Retention(RetentionPolicy.RUNTIME),意思即是指当前注释的保留政策为 RUNTIME,即存在 Java 源文件,也存在经编译器编译生成的 Class 字节码文件,同时,在运行时虚拟机( VM )中也保留该注解,可通过反射机制获取当前注解内容。

Target

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

@Target:表示该注解类型所使用的程序元素类型。当注解类型声明中没有 @Target 元注解,则默认为可适用所有的程序元素;若存在指定的 @Target 元注解,则编译器强制实施相应的使用限制。程序元素( ElementType ) 是枚举类型,共定义 8 种程序元素如下表:

ElementType 含义
ANNOTATION_TYPE 注解类型声明
CONSTRUCTOR 构造方法声明
FIELD 字段声明(包括枚举常量)
LOCAL_VARIABLE 局部变量声明
METHOD 方法声明
PACKAGE 包声明
PARAMETER 参数声明
TYPE 类、接口(包括注解类型)或枚举声明

比如,上面源码 @Target 的定义中有一行@Target(ElementType.ANNOTATION_TYPE),意思即是指当前注解的元素类型是注解类型。

自定义注解

Demo 地址:AnnotationDemo

上面了解了注解,下面来尝试自定义注解。

自定义注解与创建接口类似,注解要以 @ 开头。

首先,声明一个自定义注解如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface CoderZebron {

String name();

String host() default "iamasoldier6.com";

int level() default 1;
}

说明:

1.注解方法不带参数,没有修饰符,实际上,只允许 public 和 abstract 修饰符,默认为 public,不允许抛异常,比如 name()、host();

2.注解方法返回值类型为基本类型,String、Class、annotation、enumeration 及这些类型的数组类型;

3.注解方法可以有默认值,如default "iamasoldier6.com",默认 host=”iamasoldier6.com”;

4.若只有一个默认属性,可直接用 value() 函数,一个属性都没有表示该注解为 Mark Annotation。

其次,有了自定义注解 @CoderZebron 后,在代码中使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AnnotationDemo {

@CoderZebron(name = "Iamasoldier6", host = "iamasoldier6.com", level = 1)
public static void main(String[] args) {
System.out.println("I am main method");
}

@SuppressWarnings({"unchecked", "deprecation"})
@CoderZebron(name = "Iamasoldier6", host = "iamasoldier6.com", level = 2)
public void demo() {
System.out.println("I am demo method");
}
}

该注解的保留政策为RetentionPolicy.RUNTIME,故可在运行期通过反射来使用。

最后,通过反射机制来解析自定义注解 @CoderZeron,反射类位于包 java.lang.reflect 中,其中有一个接口AnnotatedElement,该接口定义了注解相关的几个方法如下:

返回值 方法 说明
T getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回 null。
Annotation[] getAnnotations() 返回此元素上存在的所有注解。
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注解。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 当存在该元素的指定类型注解,则返回 true,否则返回 false。

前面自定义的注解,适用对象为 Method,Method 继承自类 AccessibleObject,同时,类 AccessibleObject 实现了 AnnotatedElement 接口,则利用反射方法,来实现解析 @CoderZebron 的功能,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AnnotationParser {
public static void main(String[] args) throws SecurityException, ClassNotFoundException {
String clazz = "AnnotationDemo";
Method[] methods = AnnotationParser.class.getClassLoader().loadClass(clazz).getMethods();

for (Method method : methods) {
if (method.isAnnotationPresent(CoderZebron.class)) {
CoderZebron coder = method.getAnnotation(CoderZebron.class);
System.out.println("method: " + method);
System.out.println(
"name= " + coder.name() + ", website= " + coder.host() + ", revision= " + coder.level());
}
}
}
}

运行后的输出结果为:

1
2
3
4
method: public static void AnnotationDemo.main(java.lang.String[])
name= Iamasoldier6, host= iamasoldier6.com, revision= 1
method: public void AnnotationDemo.demo()
name= Iamasoldier6, host= iamasoldier6.com, revision= 2

通过反射将注解直接输出,不过这里只是一个简单的 Demo,注解信息可用来做很多事。

常见示例

Android 中的常见示例如下:

Override

1
2
3
4
@Override
protected void onCreate(Bundle savedInstanceState) {
//
}

Retrofit

1
2
@GET("/users/{username}")
User getUser(@Path("username") String username);

ButterKnife

1
2
@InjectView(R.id.user)
EditText mEtUsername;

至此,关于 Java 中的注解探究并总结完毕。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.http://docs.oracle.com/javase/7/docs/api/

2.http://t.cn/R4b4Fsr

3.http://gityuan.com/2016/01/23/java-annotation/