[TOC]
# 注解(Annotation)
## 本文目的
如果你是 Java 新手,之前从来没有阅读过关于注解的资料及文章,那么我希望通过本篇文章,可以让你了解注解的基本概念,读懂应用了注解的代码。
## 概念
我们先来看下面这个例子
~~~
// Chinese 类作为 Person 类的子类,重写 speak() 方法
public class Person {
public void speak() {
System.out.println("I can speak");
}
}
public class Chinese extends Person {
public void speak1() { //不小心把 speak 误写成 speak1 了
super.speak();
System.out.println("I also can speak Chinese");
}
}
~~~
重写方法时写错了方法名,这种情况编译器不会有任何警告,因为它会认为新增了一个方法。假如现在调用 Chinese.speak(),执行的还是父类的方法,显然不是需要的结果,当然你可以说:“我永远也不会犯这种低级错误”,好吧,不过我还是要向你推荐注解,因为它可以帮我们避免这种错误,而且可以增加代码的可读性。
~~~
public class Chinese extends Person {
//1.添加 @Override 注解
//2.此时编译器报错Method does not override method from its superclass
//3.发现错误,将 speak1 改为 speak ,警告消失,编译通过
@Override
public void speak1() {
super.speak();
System.out.println("I also can speak Chinese");
}
}
~~~
你可以把注解当作一个特殊的修饰符(类似 final , static)。
**@Override** 非常直观地表明这个方法是一个重写的方法,并且当我们误写的时候,编译器就会发出警告。它是 Java sdk 中自带的注解,自带的常用的注解还有 `@SuppressWarnings` , `@Deprecated` 等,可以通过搜索,了解具体的作用。
## 语法
~~~
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
~~~
这是 `@Override` 类的代码,我们就根据它的代码来学习注解的语法。
首先是声明一个注解:`public @interface Override{}`,和定义一个接口很像,唯一不同的是需要加一个 @ ,再引入一个概念:**元注解**,元注解是修饰注解的注解,下文有详细介绍。
这两行代码就是使用了 `@Target` 和 `@Retention` 2个元注解修饰了 `@Override` 注解,就好像刚刚我们在方法的声明上写了一个 `@Override` ,这里的元注解也是写在注解的声明上的。另外我们得知,所谓的“元注解修饰注解”是发生在注解类中的,而不是发生在使用注解的时候
~~~
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
-------------------------------------------------------------------------------------
//错误的示范
public class Person {
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@Override
public String toString(){
//重写 toString 方法
}
}
~~~
再来看一下 **@Target** 的代码
~~~
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
~~~
重点来看这行代码 `ElementType[] value();`,转换成通用格式应该是 `参数类型 参数名称() default 默认值;`,`default 默认值` 是可选的,因为这里的参数类型是 `ElementType[]` ,所以 `@Target({})` 使用了花括号,并且可以传入多个值,形成数组。还有一点需要注意:注解定义了参数之后,参数所传入的值的类型并非所有类型都可以,它只能是下列这些类型:
* 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
* String
* Class
* enum
* Annotation
* 以上所有类型的数组 这里的 ElementType[] 就是一个枚举类的数组
`@Target({ElementType.ANNOTATION_TYPE})` 是 `@Target( value = {ElementType.ANNOTATION_TYPE})` 的省略形式,因为注解的规则是:没有定义任何参数的注解称为**标记注解**,就像刚才的 `@Override`,如果定义了一个参数,那么最好将参数名称定义为 value,这样在传入参数的时候,可以省略 `value=` 直接写入值,当然你不定义为 value 的话,也是可以的,只不过无法使用这个省略形式。例如:
~~~
//定义一个注解,参数的值的类型设定为 String
public @interface MyAnnotation {
String value() ;
}
//没有任何问题
@MyAnnotation("HelloWorld!")
public class Person{
...
}
//假如我们不将名称写为 value
public @interface MyAnnotation{
String query();
}
//必须要写成 参数名称 = 值 的形式
@MyAnnotation(query = "HelloWorld")
public class Person{
...
}
//default 的示范
public @interface MyAnnotation {
String value() default "HelloWorld" ;
}
//不传入值的时候等价于 @MyAnnotation("HelloWorld")
//也可以传入值覆盖掉 default 值, @MyAnnotation("WorldHello")
@MyAnnotation
public class Person{
...
}
~~~
所以注解在使用时的通用格式应该是:
* 定义了参数的注解: @注解名(参数名称 = 要传入的值)
* 标记注解或者定义了 default 值的注解: @注解名
## 元注解
目前有 5 个元注解,分别是:
* Target
* Retention
* Documented
* Inhertied
* Repeatable
**@Target** 决定了你的注解可以修饰哪些对象,如果你希望自己的注解只用来修饰方法,那么用你的注解修饰成员变量的话,就会出错。我们的注解可以修饰下列这些对象,是 `java.lang.annotation` 包下的一个枚举类 `ElementType` 提供的值 :
* TYPE :用于描述类,接口,注解类,枚举类
* FIELD :全局变量(定义在类中的变量)
* METHOD :方法
* PARAMETER :方法中的参数
* CONSTRUCTOR :构造函数
* LOCAL_VARIABLE :成员变量(定义在方法中的变量)
* ANNOTATION_TYPE :希望这个注解只能修饰注解类(注意和 TYPE 区分)
* PACKAGE
* TYPE_USE
* TYPE_PARAMETER
有 3 个没有做解释的,我们分别说一下:
**PACKAGE** :从字面上看的话,我们会联想到它的使用场景,应该是这样的:
~~~
@MyAnnotation
package com.example;
public class Person{
...
}
~~~
这样做的话,我们会得到一个错误 `Package annotations should be in file package-info.java`,为什么呢?因为对于包的注释不能放在类中,而是要在这个包的目录下创建一个 package-info.java 来写注释,如果你的 Java 基础够好,你可能可以看出来," - " 这个符号是不能出现在类的命名中的(我就没看出来,╥﹏╥...),所以不能直接创建一个类,做法是在包下新建一个文件,然后将它改名为这个名字,在 Android Studio 中的操作是:右键点击一个包 -> New -> File -> 输入 package-info.java 就可以了,接下来我们只需要在 `package-info.java` 中添加这两行代码。
~~~
@MyAnnotation
package com.example; //包名,如果 MyAnnotation 是别的包中的,记得 import
~~~
**TYPE_PARAMETER** : Java 8 新增的,与**类型,参数**有关,但是它无法修饰方法中的参数,它可以修饰泛型中的类型参数,像这样:
~~~
public class Test<@MyAnnotation T> {
...
}
~~~
**TYPE_USE** :Java 8 新增的,可以用于任何涉及到**类型**的情况,如果选取这个值,相当于你直接实现了这些值所实现的功能,不仅如此,还可以修饰其他的一些对象,可以参考[Type](https://dzone.com/articles/java-8-type-annotations)
* TYPE
* FIELD
* PARAMETER
* CONSTRUCTOR
* LOCAL_VARIABLE
* TYPE_PARAMETER
不过它仍然无法修饰方法和包。
**@Retention** 决定了注解的生命周期,`java.lang.annotation` 包下有一个枚举类 `RetentionPolicy` , 它提供了注解可选的生命周期,不同的生命周期往往决定了注解何时发挥它的作用(如果该注解我们只希望它在编译的时候产生作用,那我们就不需要把它保留到程序运行的时候)。
* SOURCE :只在 .java 文件中保留
* CLASS : 将 .java 文件编译成 .class 文件后,还会保留在 .class 文件中
* RUNTIME :程序运行时,注解仍然有效
**@Documented** 决定了是否需要在生成的 Api 说明中体现该注解。例如,我们现在用一个注解修饰了一个方法
~~~
@MyAnnotation //这里的 @MyAnnotation 使用了 @Documented 修饰
public void speak(){
}
~~~
下面是使用 JavaDoc 生成d 2 份文档,体现了 @Documented 的作用:


**@Inherited** 决定了注解是否可以被继承,这里的继承不是针对注解与注解之间的继承,仍然是针对 Java 类中的继承,具体的作用在后面的“简单应用”中说明。
**@Repeatable**是 Java 8 新增的,具体的作用在后面的“简单应用”中说明。
## 简单应用
~~~
//我们自定义了一个 Annotation
@Retention(RetentionPolicy.RUNTIME)//这里定义为 RUNTIME ,可以配合反射使用
public @interface MyAnnotation {
String value();
}
//抽象类 InheritedTest 使用了 @MyAnnotation
@MyAnnotation("ClassAnnotation")
public abstract class InheritedTest {
@MyAnnotation("NormalMethod")
public void normalMethod() {
}
@MyAnnotation("ImplementsMethod")
public abstract void implementsMethod();
@MyAnnotation("OverrideMethod")
public void overrideMethod() {
}
}
~~~
我们的问题是:如果现在有一个类继承了 `InheritedTest` 类,那么在 InheritedTest 中定义的注解会不会都被继承?`MyAnnotation` 有没有用 `@Inherited` 修饰会不会影响结果?我们先来证明这个问题。
~~~
//定义一个子类
public class SubClass extends InheritedTest {
@Override public void implementsMethod() {
}
@Override public void overrideMethod() {
}
}
//main 方法
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = SubClass.class;
Method methodAbstract = clazz.getMethod("implementsMethod");
Method methodOverride = clazz.getMethod("overrideMethod");
Method methodNormal = clazz.getMethod("normalMethod");
//检查修饰类的注解是否被继承
if (clazz.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
System.out.println("子类继承到父类类上Annotation,其信息如下:" + myAnnotation.value());
} else {
System.out.println("子类没有继承到父类的 Annotation");
}
//检查实现抽象的方法的注解是否被继承
checkAnnotation(methodAbstract);
//检查重写了父类方法,注解是否可以被继承
checkAnnotation(methodOverride);
//检查子类直接继承的方法,注解是否可以被继承
checkAnnotation(methodNormal);
}
public static void checkAnnotation(Method method) {
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
System.out.println(method.getName() + ":继承到父类方法上的Annotation,其信息如下" + myAnnotation.value());
} else {
System.out.println(method.getName() + "没有继承到父类方法上的Annotation");
}
}
}
result :
子类没有继承到父类的 Annotation
implementsMethod没有继承到父类方法上的Annotation
overrideMethod没有继承到父类方法上的Annotation
normalMethod:继承到父类方法上的Annotation,其信息如下NormalMethod
(当 MyAnnotation 被 @Inherited 修饰时的结果)
result:
子类继承到父类类上Annotation,其信息如下:ClassAnnotation
implementsMethod没有继承到父类方法上的Annotation
overrideMethod没有继承到父类方法上的Annotation
normalMethod:继承到父类方法上的Annotation,其信息如下NormalMethod
~~~
通过上述的结果,我们可以得出这样的结论:`@Inherited` 只会影响修饰类的注解的继承,关于方法的注解,即使不通过 `@Inherited`,子类继承了方法后,也可以继承到注解,但如果是通过“实现”或者“重写”的方法,那么就无法继承到注解。
下面这个例子是关于 **使用@Repeatable**
~~~
//我们现在想要使用这样一个效果:让 Test 类被 MyAnnotation 修饰 2 次
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
@MyAnnotation("Hello")
@MyAnnotation("World")
public class Test {
...
}
~~~
这样做是行不通的,同样的注解不能多次修饰同一个对象,但是如果一定要要求这样做,那么可以这样:
~~~
//再定义一个注解,将参数接收的值的类型设置为 MyAnnotation[] ,即可以接收多个 MyAnnotation
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationArray {
MyAnnotation[] value();
}
//main 方法
@AnnotationArray({ @MyAnnotation("Hello"), @MyAnnotation("World") })
public class Test {
public static void main(String[] args) {
Class clazz = Test.class;
AnnotationArray annotationArray =
(AnnotationArray) clazz.getDeclaredAnnotation(AnnotationArray.class);
for (MyAnnotation myAnnotation : annotationArray.value()) {
System.out.println(myAnnotation.value());
}
}
}
result :
Hello
World
~~~
我们完成了这个效果,不过在使用 `@AnnotationArray` 的时候显得过于复杂,`@Repeatable` 可以改善这一情况。
~~~
@Repeatable(AnnotationArray.class) //使用 @Repeatable
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
@AnnotationArray 的代码不变
@MyAnnotation("Hello") //现在可以使用这种多次注解的形式了
@MyAnnotation("World")
public class Test {
public static void main(String[] args) {
Class clazz = Test.class;
MyAnnotation[] myAnnotations =
(MyAnnotation[]) clazz.getDeclaredAnnotationsByType(MyAnnotation.class);
for (MyAnnotation myAnnotation : myAnnotations) {
System.out.println(myAnnotation.value());
}
}
}
result :
Hello
World
~~~
**@Repeatable** 在使用时需要传入一个 Class 的值,这个 Class 是一个注解类的 Class,而且这个注解的参数接受的值的类型应该是一个数组,数组的类型就是我们需要多次使用的注解,所以在这个例子中我们传入 `AnnotationArray.class` 。
## 参考
[ElementType.PACKAGE的使用](http://www.cnblogs.com/ycoe/archive/2009/09/26/1574565.html)
[@Inherited的测试](http://elf8848.iteye.com/blog/1621392)
[Repeatable 的使用](http://www.jianshu.com/p/28edf5352b63)
[Java注解全面解析](http://9leg.com/java/2016/01/21/java-annotation.html)
[一小时搞明白自定义注解](http://blog.csdn.net/u013045971/article/details/53433874)
