Java高级程序设计

注解

注解

  • 从JavaSE 1.5开始,Java增加了对元数据的支持,也就是Annotation
    • 元数据是关于数据的数据。在编程语言上下文中,元数据是添加到程序元素如方法、字段、类和包上的额外信息,对数据进行说明描述的数据
  • Annotation其实就是对代码的一种特殊标记,这些标记可以在编译,类加载和运行时被读取,并执行相应的处理

基本的Annotation

  • @Override — 限定重写父类方法
  • @Deprecated — 标示已过时
  • @SuppressWarnings — 抑制编译器警告
  • @SafeVarargs — 抑制可变参数警告(Java 7+)
  • @FunctionalInterface — 标记函数式接口(Java 8+)

@Override

  • 用于告知编译器,需要覆写父类的当前方法
    • 如果某个方法带有该注解但并没有覆写父类相应的方法,则编译器会生成一条错误信息
    • @Override可适用元素为方法

@Deprecated

  • 使用这个注解,用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用了(即过时了)
  • 调用具有该注解的方法时编译器会出现警告,告知该方法已过时。
  • @Deprecated可适合用于除注解类型声明之外的所有元素

@SuppressWarnings

  • 用于告知编译器忽略特定的警告信息
    • 例在泛型中使用原生数据类型,编译器会发出警告,当使用该注解后,则不会发出警告
  • 该注解有方法value()属性,可支持多个字符串参数,用于指定忽略哪种警告
    • @SuppressWarnings(value={"uncheck","deprecation"})

Values

参数 含义
deprecation 使用了过时的类或方法
unchecked 执行了未检查的转换
fallthrough switch块缺少了break
finally 任意finally子句不能正常完成
... ...
all 以上所有情况

javac -X

https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html

@SafeVarargs

  • Java 7引入,用于抑制可变参数(varargs)相关的警告
  • 只能用于构造方法、静态方法、final方法或private实例方法
  • 用于标记方法对可变参数的处理是安全的
@SafeVarargs
public static <T> void print(T... items) {
    for (T item : items) {
        System.out.println(item);
    }
}

@FunctionalInterface

  • Java 8引入,用于标记函数式接口
  • 函数式接口是只有一个抽象方法的接口
  • 如果接口不符合函数式接口的定义,编译器会报错
@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
    // 可以有默认方法和静态方法
    default void print() {
        System.out.println("Calculator");
    }
}

元Annotation

  • 元Annotation就是修饰其他Annotation的Annotation
  • Java除了在java.lang提供了上述内建注解外,还在java.lang.annotation包下提供了6个Meta Annotation(元Annotation)
    • 其中有5个元Annotation都用于修饰其他的Annotation定义,@Repeatable专门用于定义Java 8新增的可重复注解

Annotation Type Override

@Target(value=METHOD)
@Retention(value=SOURCE)
public @interface Override {

}

https://docs.oracle.com/javase/8/docs/api/java/lang/Override.html

其中@interface定义了Override是一个Annotation类型,或者叫元数据(meta-data)。 @Target@Retention是对@Override的注解,称之为元注解(注解的注解)。

@Target的定义

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type can be applied to.
     * @return an array of the kinds of elements an annotation type can be applied to
     */
    ElementType[] value();
}

Target也是一个注解类型,在其内部定义了方法ElementType[] value();返回值就是@Target(ElementType.METHOD)中的ElementType.METHOD,也就是注解的属性,是一个ElementType枚举。

ElementType

这个枚举其实就是定义了注解的适用范围,在Override注解中,@Target的属性是ElementType.METHOD,所以Override这个注解只能用于注解方法。

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */                                                              
    TYPE,
    /** 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,
    /** Package declaration */
    PACKAGE,
    /** Type parameter declaration */
    TYPE_PARAMETER,
    /** Use of a type */
    TYPE_USE
}

注意TYPE_PARAMETER(Java 8)用于类型参数声明,如泛型参数;TYPE_USE(Java 8)用于任何类型使用的地方,包括类型转换、instanceof检查等。

再看@Target

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type can be applied to.
     * @return an array of the kinds of elements an annotation type can be applied to
     */
    ElementType[] value();
}

Target注解本身也有一个@Target元注解,这个@Target元注解属性是ElementType.ANNOTATION_TYPE,也就是说Target注解只能用作元数据(注解)的注解,所以叫它元注解。

@Retention(RetentionPolicy.RUNTIME)? @Documented?

@Retention

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

RetentionPolicy

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,
    /**
     * 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,
    /**
     * 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.
     */
    RUNTIME
}

@Retention注解则定义了注解的保留范围,如:在源代码、CLASS文件或运行时保留。超出@Retention定义的属性,注解将被丢弃。

@Documented

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

如果一个注解定义了@Documented,在javadoc生成API文档时,被这个注解标记的元素在文档上也会出现该注解。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InWork {
    String value();
}

/**
 * Annotated class.
 */
@InWork(value = "")
public class MainApp {...}

@Inherited

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

只对@TargetElementType.TYPE(类、接口、枚举)有效,并且只支持类元素。使用了@Inherited的注解修饰在一个class上,可以保证继承它的子类也拥有同样的注解。

@Repeatable

  • Java 8引入,用于定义可重复注解
  • 允许在同一个元素上多次使用同一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Schedules.class)
public @interface Schedule {
    String dayOfMonth() default "first";
    String dayOfWeek() default "Mon";
}
public class Task {
    @Schedule(dayOfMonth="last")
    @Schedule(dayOfWeek="Fri")
    public void doPeriodicCleanup() { }
}

自定义注解

使用@interface可以定义一个注解

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAnno{
    //以下无参方法实际上定义的是注解的属性
    //其返回值可以是所有基本类型、String、Class、enum、Annotation或以上类型的数组
    String name();
    //default关键字可以定义该属性的默认值,如果没有指定默认值在使用注解时必须显示指定属性值
    String website() default "hello";
    int revision() default 1;
}

注解属性的特殊规则

value() 的特殊性

如果注解只有一个属性且名为value(),在使用时可以省略属性名:

// 定义
public @interface Author {
    String value();  // 只有value属性
}

// 使用 - 两种方式等价
@Author("张三")
@Author(value="张三")

使用自定义注解

public class AnnotationDemo {
    @AuthorAnno(name="lvr", website="hello", revision=1)
    public static void main(String[] args) {
        System.out.println("I am main method");
    }

    @SuppressWarnings({ "unchecked", "deprecation" })
    @AuthorAnno(name="lvr", website="hello", revision=2)
    public void demo(){
        System.out.println("I am demo method");
    }
}

注解解析

方法上的注解

public class AnnotationParser {
    public static void main(String[] args) throws SecurityException, ClassNotFoundException {
        Method[]  demoMethod = AnnotationDemo.class.getMethods();
        
        for (Method method : demoMethod) {

            if (method.isAnnotationPresent(AuthorAnno.class)) {
                 AuthorAnno annotationInfo = method.getAnnotation(AuthorAnno.class);
                 System.out.println("method: "+ method);
                 System.out.println("name= "+ annotationInfo.name() +
                         " , website= "+ annotationInfo.website()
                        + " , revision= "+annotationInfo.revision());
            }
        }
    }
}

类上的注解

@AuthorAnno(name="ClassAuthor", website="example.com", revision=1)
public class MyClass {
    // ...
}

// 解析
Class<?> clazz = MyClass.class;
if (clazz.isAnnotationPresent(AuthorAnno.class)) {
    AuthorAnno annotation = clazz.getAnnotation(AuthorAnno.class);
    System.out.println("Class author: " + annotation.name());
}

字段上的注解

public class MyClass {
    @AuthorAnno(name="FieldAuthor", website="example.com", revision=1)
    private String field;
}

// 解析
Field[] fields = MyClass.class.getDeclaredFields();
for (Field field : fields) {
    if (field.isAnnotationPresent(AuthorAnno.class)) {
        AuthorAnno annotation = field.getAnnotation(AuthorAnno.class);
        System.out.println("Field: " + field.getName() + 
                          ", Author: " + annotation.name());
    }
}

参数上的注解

public class MyClass {
    public void method(@AuthorAnno(name="ParamAuthor") String param) {
        // ...
    }
}

// 解析
Method method = MyClass.class.getMethod("method", String.class);
Annotation[][] paramAnnotations = method.getParameterAnnotations();
for (int i = 0; i < paramAnnotations.length; i++) {
    for (Annotation annotation : paramAnnotations[i]) {
        if (annotation instanceof AuthorAnno) {
            AuthorAnno authorAnno = (AuthorAnno) annotation;
            System.out.println("Parameter " + i + " author: " + authorAnno.name());
        }
    }
}

获取所有注解

// 获取类上的所有注解(包括继承的)
Annotation[] annotations = MyClass.class.getAnnotations();

// 获取类上直接声明的注解(不包括继承的)
Annotation[] declaredAnnotations = MyClass.class.getDeclaredAnnotations();

// 获取方法上的所有注解
Method method = MyClass.class.getMethod("methodName");
Annotation[] methodAnnotations = method.getAnnotations();

注解处理器(Annotation Processor)

注解处理器在编译时处理注解,可以生成代码、验证代码等。

@SupportedAnnotationTypes("com.example.AuthorAnno")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AuthorAnnoProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, 
                          RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(AuthorAnno.class)) {
            AuthorAnno annotation = element.getAnnotation(AuthorAnno.class);
            // 处理注解,可以生成代码、验证等
            processingEnv.getMessager().printMessage(
                Diagnostic.Kind.NOTE, 
                "Found @AuthorAnno: " + annotation.name()
            );
        }
        return true;
    }
}

如何使用注解处理器

编译时通过 -processor 参数指定:

javac -processor com.example.AuthorAnnoProcessor MyClass.java

或者使用 -processorpath 指定处理器所在的jar包:

javac -processorpath processor.jar -processor com.example.AuthorAnnoProcessor MyClass.java

验证注解处理器是否生效

编译时查看输出信息:

javac -processor com.example.AuthorAnnoProcessor -XprintProcessorInfo MyClass.java

如果看到处理器输出的消息(如 Found @AuthorAnno: ...),说明处理器已成功运行。

编译时处理 vs 运行时反射

特性 注解处理器 运行时反射
处理时机 编译时 运行时
性能 无运行时开销 有反射开销
能力 可生成代码 只能读取注解
用途 代码生成、验证 框架配置、依赖注入

注解的实际应用场景

框架/类别 注解 说明
Spring框架 @Component 标记为Spring组件
@Autowired 自动注入依赖
@RequestMapping 映射HTTP请求
JPA/Hibernate @Entity 标记为实体类
@Table 指定数据库表名
@Column 映射数据库列
JUnit @Test 标记测试方法
@Before 测试前执行
@After 测试后执行
Jackson @JsonProperty 指定JSON属性名
@JsonIgnore 忽略序列化

--- ## ElementType.TYPE_PARAMETER 和 TYPE_USE **TYPE_PARAMETER** - 用于泛型类型参数 ```java @Target(ElementType.TYPE_PARAMETER) public @interface TypeParam {} class Example<@TypeParam T> { T field; } ``` **TYPE_USE** - 用于任何类型使用的地方 ```java @Target(ElementType.TYPE_USE) public @interface NotNull {} public class Example { @NotNull String name; void method(@NotNull String param) { @NotNull String local = "value"; } } ```