# 🎯 注解

# 注解基础

注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有以下四方面:

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

注解的常见分类:

  • Java内置注解,使用这些注解标明后,编译器就会进行检查,Java自带的标准注解包括:
    • @Override | 标明重写某个方法
    • @Deprecated | 标明某个类或方法过时
    • @SuppressWarnings | 标明要忽略的警告
  • 元注解,元注解是用于定义注解的注解,包括:
    • @Retention | 用于标明注解被保留的阶段
    • @Target | 用于标明注解使用的范围
    • @Inherited | 用于标明注解可继承
    • @Documented | 用于标明是否生成javadoc文档
  • 自定义注解,可以根据自己的需求自定义注解(就像定义类那样),自定义注解需要用到元注解(这就是元注解的作用)。

接下来我们通过这个分类角度来理解注解。

# Java内置注解

Java 1.5开始自带的标准注解,包括@Override@Deprecated@SuppressWarnings

# @Override

@Override:表示当前的方法定义将覆盖父类中的方法。

# 注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
1
2
3
4
1
2
3
4

从它的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。

这个注解的作用我们大家都不陌生,那就是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。

# 使用示例
class A{
    public void test() {}
}

class B extends A{
    /**
     * 重载父类的test方法
     */
    @Override
    public void test() {}
}
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11

# @Deprecated

@Deprecated:表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告。

# 注解定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
1
2
3
4
5
1
2
3
4
5

从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。

这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。

# 使用示例
class A{
    /**
     * 被弃用的方法
     */
    @Deprecated
    public void oldMethod() {}
}
1
2
3
4
5
6
7
1
2
3
4
5
6
7

# @SuppressWarnings

@SuppressWarnings:表示关闭编译器警告信息,提供参数来告诉编译器具体关闭哪一些警告信息

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

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为 String[]

它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:

参数 作用 原描述
all 抑制所有警告 to suppress all warnings
boxing 抑制装箱、拆箱操作时候的警告 to suppress warnings relative to boxing/unboxing operations
cast 抑制映射相关的警告 to suppress warnings relative to cast operations
dep-ann 抑制启用注释的警告 to suppress warnings relative to deprecated annotation
deprecation 抑制过期方法警告 to suppress warnings relative to deprecation
fallthrough 抑制确在switch中缺失breaks的警告 to suppress warnings relative to missing breaks in switch statements
finally 抑制finally模块没有返回的警告 to suppress warnings relative to finally block that don’t return
hiding 抑制与隐藏变数的区域变数相关的警告 to suppress warnings relative to locals that hide variable()
incomplete-switch 忽略没有完整的switch语句 to suppress warnings relative to missing entries in a switch statement (enum case)
nls 忽略非nls格式的字符 to suppress warnings relative to non-nls string literalsn
ull 忽略对null的操作 to suppress warnings relative to null analysis
rawtype 使用generics时忽略没有指定相应的类型 to suppress warnings relative to un-specific types when using
restriction 抑制与使用不建议或禁止参照相关的警告 to suppress warnings relative to usage of discouraged or
serial 忽略在serializable类中没有声明serialVersionUID变量 to suppress warnings relative to missing serialVersionUID field for a serializable class
static-access 抑制不正确的静态访问方式警告 to suppress warnings relative to incorrect static access
synthetic-access 抑制子类没有按最优方法访问内部类的警告 to suppress warnings relative to unoptimized access from inner classes
unchecked 抑制没有进行类型检查操作的警告 to suppress warnings relative to unchecked operations
unqualified-field-access 抑制没有权限访问的域的警告 to suppress warnings relative to field access unqualified
unused 抑制没被使用过的代码的警告 to suppress warnings relative to unused code
# 使用示例
class A {
    /**
     * 忽略警告
     */
    @SuppressWarnings("rawtypes")
    public List processList() {
        List list = new ArrayList();
        return list;
    }
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9

# 元注解

上述内置注解的定义中使用了一些元注解(注解类型进行注解的注解类),

  • 在JDK 1.5中提供了4个标准的元注解:@Target@Retention@Documented@Inherited,
  • 在JDK 1.8中提供了两个元注解 @Repeatable@Native

# @Target

@Target:描述注解的使用范围(即:被修饰的注解可以用在什么地方)。

Target 注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在 ElementType 枚举中

public enum ElementType {
    TYPE,             // 类、接口、枚举类
    FIELD,            // 成员变量(包括:枚举常量)
    METHOD,           // 成员方法
    PARAMETER,        // 方法参数
    CONSTRUCTOR,      // 构造方法
    LOCAL_VARIABLE,   // 局部变量
    ANNOTATION_TYPE,  // 注解类
    PACKAGE,          // 可用于修饰:包
    TYPE_PARAMETER,   // 类型参数,JDK 1.8 新增
    TYPE_USE          // 使用类型的任何地方,JDK 1.8 新增
}
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12

# @Reteniton

Reteniton注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)。

Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在 RetentionPolicy 枚举中

public enum RetentionPolicy {
    SOURCE,      // 源文件保留
    CLASS,       // 编译期保留,默认值
    RUNTIME      // 运行期保留,可通过反射去获取注解信息
}
1
2
3
4
5
1
2
3
4
5

# @Documented

Documented 注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。

例子:以下代码在使用Javadoc工具可以生成@TestDocAnnotation注解信息。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
 
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestDocAnnotation {
 
	public String value() default "default";
}
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10

# @Inherited

Inherited 注解的作用:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited 修饰的Annotation,则其子类将自动具有该注解。

例子

例子
  1. 定义@Inherited注解:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestInheritedAnnotation {
    String [] values();
    int number();
}
1
2
3
4
5
6
7
1
2
3
4
5
6
7
  1. 在父类使用这个注解:
@TestInheritedAnnotation(values = {"value"}, number = 10)
public class Person {}
1
2
1
2
  1. 子类继承父类,但不使用注解。通过 Class类查看该子类所拥有的注解:
class Student extends Person{
	@Test
    public void test(){
        Class clazz = Student.class;
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.toString());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
  1. 输出结果:

可以看到下面的输出,即使 Student 类没有显示地被注解@TestInheritedAnnotation,但是它的父类Person被注解,而且@TestInheritedAnnotation@Inherited注解,因此Student类自动有了该注解。

xxxxxxx.TestInheritedAnnotation(values=[value], number=10)
1
1

# @Repeatable (Java8)

# @Native (Java8)

# 自定义注解

见识了元注解和内置注解,自定义注解就显得理所当然了。

例子

  1. 定义自己的注解:
package com.pdai.java.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethodAnnotation {

    public String title() default "";

    public String description() default "";

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 使用自定义注解
package com.pdai.java.annotation;

import java.io.FileNotFoundException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class TestMethodAnnotation {

    @Override
    @MyMethodAnnotation(title = "toStringMethod", description = "override toString method")
    public String toString() {
        return "Override toString method";
    }

    @Deprecated
    @MyMethodAnnotation(title = "old static method", description = "deprecated old static method")
    public static void oldMethod() {
        System.out.println("old method, don't use it.");
    }

    @SuppressWarnings({"unchecked", "deprecation"})
    @MyMethodAnnotation(title = "test method", description = "suppress warning static method")
    public static void genericsTest() throws FileNotFoundException {
        List l = new ArrayList();
        l.add("abc");
        oldMethod();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  1. 用反射接口获取注解信息
public static void main(String[] args) {
    try {
        // 获取所有methods
        Method[] methods = TestMethodAnnotation.class.getClassLoader()
                .loadClass(("com.pdai.java.annotation.TestMethodAnnotation"))
                .getMethods();

        // 遍历
        for (Method method : methods) {
            // 方法上是否有MyMethodAnnotation注解
            if (method.isAnnotationPresent(MyMethodAnnotation.class)) {
                try {
                    // 获取并遍历方法上的所有注解
                    for (Annotation anno : method.getDeclaredAnnotations()) {
                        System.out.println("Annotation in Method '"
                                + method + "' : " + anno);
                    }

                    // 获取MyMethodAnnotation对象信息
                    MyMethodAnnotation methodAnno = method
                            .getAnnotation(MyMethodAnnotation.class);

                    System.out.println(methodAnno.title());

                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
        }
    } catch (SecurityException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  1. 输出结果:
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @java.lang.Deprecated()
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @com.pdai.java.annotation.MyMethodAnnotation(title=old static method, description=deprecated old static method)
old static method
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.genericsTest() throws java.io.FileNotFoundException' : @com.pdai.java.annotation.MyMethodAnnotation(title=test method, description=suppress warning static method)
test method
Annotation in Method 'public java.lang.String com.pdai.java.annotation.TestMethodAnnotation.toString()' : @com.pdai.java.annotation.MyMethodAnnotation(title=toStringMethod, description=override toString method)
toStringMethod
1
2
3
4
5
6
7
1
2
3
4
5
6
7

# 注解与反射接口

在上面我们提到了反射,为什么会用到反射?

思考一个问题:定义注解后,如何获取注解中的内容呢?

这就引出了反射,因为反射包 java.lang.reflect 下的 AnnotatedElement 接口提供了获取注解的方法。

注意

只有注解的 @Reteniton 被定义为 RUNTIME 后,该注解才能是运行时可见,当class文件被装载时,被保存在class文件中的Annotation才会被虚拟机读取。

AnnotatedElement 接口是所有程序元素(ClassMethodConstructor)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象之后,程序就可以调用该对象的方法来访问 Annotation 信息。我们看下具体的相关接口:

  1. getAnnotations 返回所有注解:

Annotation[] getAnnotations()

  1. getDeclaredAnnotation 返回所有注解(但忽略继承的注解):

<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)

  1. getAnnotation 返回指定类型的注解:

<T extends Annotation> T getAnnotation(Class<T> annotationClass)

  1. getAnnotationsByType 返回指定类型的注解数组:

<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)

  1. getDeclaredAnnotationsByType 返回指定类型的注解数组(但忽略继承的注解):

<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)

  1. getDeclaredAnnotations 返回所有注解(但忽略继承的注解)及注解对应的重复注解容器:

Annotation[] getDeclaredAnnotations()

  1. isAnnotationPresent 判断是否包含指定类型的注解:

boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)