文章目录

  • 1. 什么是Java注解
  • 2. 基本语法
  • 2.1 声明注解和元注解
  • 2.2 @Target
  • 2.3 @Retention
  • 2.4 注解元素及其数据类型
  • 2.5 编译器对默认值的限制
  • 2.6 注解不支持继承
  • 2.6 快捷方式(value)
  • 3. Java内置注解与其它元注解
  • 4. 注解和反射机制


1. 什么是Java注解

Java注解(Annotation)又称为Java标注,是JDK5引入的一种机制,Java语言中的类,方法,变量,参数和包等都可以标注。鉴于目前大部分框架(如Spring)都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于一个Java工程师是来说是很有必要的事。

实际上Java注解没有大家想的那么难,跟普通修饰符(public static void)的使用方法并没有多大区别,下面的例子是常见的注解。

public class AnnotationDemo {
    //@Test注解修饰方法testA
    @Test
    public  void testA(){
        System.out.println("testA.....");
    }

    //一个方法上可以拥有多个不同的注解
    @Deprecated
    @SuppressWarnings("all")
    public  void testB(){

    }
}

通过在方法上使用@Test注解后,在运行该方法时,测试框架会自动识别该方法并单独调用,@Test实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。而对于@Deprecated和@SuppressWarnings(“all”),则是Java本身内置的注解,在代码中,可以经常看见它们,但这并不是一件好事,毕竟当方法或是类上面有@Deprecated注解时,说明该方法或是类都已经过期不建议再用,@SuppressWarnings 则表示忽略指定警告,比如@SuppressWarnings(“all”),这就是注解的最简单的使用方式,有兴趣了解SuppressWarnings中值的可以参考下图:

java 注解 方法 value java @注解_编译器

2. 基本语法

2.1 声明注解和元注解

我们先来看看前面的Test注解是如何声明的:

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

我们使用了@interface来声明Test注解。并使用了Target注解传入ElementType.METHOD参数来标明@Test只能用在方法上,@Retention(RetentionPolicy.RUNTIME)则用来表示该注解生存期是运行时,可以被JVM加载识别。从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.class文件。对于@Target@Retention是由Java提供的元注解,什么是元注解呢?就是标记其他注解的注解,下面会分别介绍。

2.2 @Target

@Target注解用来约束注解可以应用的地方(如方法,类,字段),ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。其定义如下:

public enum ElementType {
   //类,接口(包括注解类型)和枚举声明
    TYPE,

    //字段声明,包括枚举常量
    FIELD,

    //方法声明
    METHOD,

    //参数声明
    PARAMETER,

    // 构造器函数声明
    CONSTRUCTOR,

    //局部变量声明
    LOCAL_VARIABLE,

    //注解类型声明
    ANNOTATION_TYPE,

    //包声明
    PACKAGE,

    /**
     * 标明注解可以用于类型参数声明(1.8新加入)
     * @since 1.8
     */
    TYPE_PARAMETER,

     /**
     * 类型使用声明(1.8新加入)
     * @since 1.8
     */
    TYPE_USE
}

注意:
当注解未指定Target值时,则此注解可以作用在任何元素之上,多个值可以使用{}并用逗号隔开。如下:

@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})

新增的两种ElementType
在Java8中ElementType新增了两个枚举成员,TYPE_PARAMETERTYPE_USE。在java8之前注解只能标注在一个声明(如类,字段,方法)上。新增的TYPE_PARAMETER可以用来标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)。如下所示

//TYPE_PARAMETER 标注在类型参数上
class D<@Parameter T> { }

//TYPE_USE则可以用于标注任意类型(不包括class)
//用于父类或者接口
class Image implements @Rectangular Shape { }

//用于构造函数
new @Path String("/usr/bin")

//用于强制转换和instanceof检查,注意这些注解中用于外部工具,它们不会对类型转换或者instanceof的检查行为带来任何影响。
String path=(@Path String)input;
if(input instanceof @Path String)

//用于指定异常
public Person read() throws @Localized IOException.

//用于通配符绑定
List<@ReadOnly ? extends Person>
List<? extends @ReadOnly Person>

@NotNull String.class //非法,不能标注class
import java.lang.@NotNull String //非法,不能标注import

这里主要说明一下TYPE_USE,类型注解用来支持在Java的程序中做强类型检查,配合第三方插件工具(如Checker Framework),可以在编译期检测出runtime error(如UnsupportedOperationException、NullPointerException异常),避免异常延续到运行期才发现,从而提高代码质量,这就是类型注解的主要作用。总之Java 8 新增加了两个注解的元素类型ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER ,通过它们,我们可以把注解应用到各种新场合中。

2.3 @Retention

@Retention用来约束注解的生命周期,分别有三个字,源码级别(SOURCE),类文件级别(CLASS)和运行时级别(RUNTIME)。其含义如下:

package java.lang.annotation;
public enum RetentionPolicy {
    //Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了
    SOURCE,       
    //编译器将Annotation存储于类对应的.class文件中。但不会加载到JVM中。默认行为 
    CLASS,       
    // 编译器将Annotation存储于class文件中,并且可由JVM读入,因此运行时我们可以获取。
    RUNTIME       
}
  • SOURCE:若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override" 就没有任何作用了。
  • CLASS:注解在class文件中可用,但会被JVM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等。
  • RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。

2.4 注解元素及其数据类型

通过上述对@Test注解的定义,我们了解了注解定义的过程,由于@Test注解没有定义其他元素,所以@Test也称为标记注解(marker annotation),但在自定义注解中,一般都会包含一些元素以表示某些值,方便处理器使用,这点在下面的例子将会看到:

@Retention(RetentionPolicy.RUNTIME)//保存到运行时
@Target(ElementType.TYPE) //只能作用于类上
public @interface MyAnnotation {
    String name() default "jerry";
    int age();
}

上述定义了一个名为MyAnnotation 的注解,该注解主要给类添加属性,与前面Test注解不同的是,我们声明一个String类型的name元素,其默认值为jerry,但是必须注意到对应任何元素的声明应采用方法的声明方式,同时可选择使用default提供默认值。

@MyAnnotation 使用方式如下:

@MyAnnotation(age = 25)
public class User {
}

关于注解支持的元素数据类型除了上述的String,还支持如下数据类型

  1. 基本类型:byte,short,int,long,boolean,float,double,char
  2. String
  3. Class
  4. Enum
  5. Annotation
  6. 上述类型的数组

注意:
倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解。

2.5 编译器对默认值的限制

编译器对元素的默认值要求非常高:

  1. 首先要求元素不能有不确定的值,也就是说元素要么有默认值,要么在使用注解时提供默认值
  2. 对于非基本类型的元素,无论是在源码中声明,还是在使用注解时提供定义默认值,都不能以null作为值,这就是限制。
  3. 为了绕开限制,我们可以根据注解定义一些特殊的值,例如空字符串或者负数,表示某个元素不存在。

2.6 注解不支持继承

注解是不支持继承的,因此不能使用关键字extends来继承某个@Annotation。但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口,这里我们反编译前面定义的MyAnnotation注解

package com.example.day10;

import java.lang.annotation.Annotation;
//反编译后的代码
public interface MyAnnotation extends Annotation
{
    public abstract String name();
     public abstract int age();
}

虽然反编译后发现MyAnnotation 注解继承了Annotation接口,请记住,即使Java的接口可以实现多继承,但定义注解时依然无法使用extends关键字继承@interface。

2.6 快捷方式(value)

所谓的快捷方式就是注解中定义了名为value的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用key=value的语法。而只需在括号中给出value元素所需要的值即可,这可以应用于任何合法类型的元素,记住,这限制了元素名必须为value,简单案例如下

//定义注解
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
@Target(ElementType.TYPE) //只能作用于类上
public @interface MyAnnotation {
    String name() default "jerry";
    int value();
}

//@MyAnnotation(25)//当只想给value赋值时,可以使用以下快捷方式
@MyAnnotation(name = "tom",value = 9)
//当name也需要赋值时必须采用key=value的方式赋值
class Person implements Serializable {

}

3. Java内置注解与其它元注解

Java定义了一套内置注解,共有10个,6个在java.lang中,剩下的4个在java.lang.annotation中

作用在代码的注解是

  1. @Override:检查该方法是否是重写方法,如果发现其父类,或者实现的接口中没有该方法会编译错误,源码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  1. @Deprecated:标记过时方法,如果使用该方法,会报编译警告。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
  1. @SuppressWarnings:指示编译器去忽略注解中声明的警告。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}
  1. @SafeVarargs:Java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
  1. @FunctionInterface:Java8开始支持,标识一个匿名函数或者函数式接口。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
  1. @Repeatable :Java 8 开始支持,标识某注解可以在同一个声明上使用多次。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}

作用在其他注解的注解(或者说 元注解)是:

  1. @Retention:标识这个注解怎么保存,是保存在源文件中,还是class文件中,又或者运行时可以通过反射访问。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
  1. @Target:标记这个注解可以修饰哪些 Java 成员。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
  1. @Documented :标记这些注解是否包含在用户文档中。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

接下来我们就用一个生成一个用户文档

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {
}


//没有使用@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {
}


@DocumentA
@DocumentB
public class DocumentDemo {
    public void Documented(){

    }
}

有两种办法可以生成doc文档,一个是使用jdk自带的javadoc,另外一个是使用工具,我们这里使用idea的工具生成。

1. 在IDEA中选择工具(Tool)选项卡打开并选择Generate JavaDoc(生成javaDoc)

java 注解 方法 value java @注解_编译器_02

2.配置各种参数

java 注解 方法 value java @注解_编译器_03


第一步:选择生成JavaDoc文档的范围,我只对一个源文件生成Doc,所以选择文件。

第二步:输出目录最好新建一个文件夹,比较有条理和整洁

第三步:区域设置,决定文档的语言,简体中文就是zh_CN、繁体(台湾)zh_tw、繁体(香港)zh-hk、英语(香港)en-hk、英语(美国)en-us、英语(英国)en-gb、英语(全球)en-ww

第四步:其他命令行参数:如果区域设置为中国,参数一般为

-encoding UTF-8 -charset UTF-8 主要意义是为了显示中文不出现乱码

第五步:设置完成后点击确定即可生成Doc文档

如下图:

java 注解 方法 value java @注解_编译器_04


可以发现使用@Documented元注解定义的注解(@DocumentA)将会生成到javadoc中,而@DocumentB则没有在doc文档中出现,这就是元注解@Documented的作用。

4. @Inherited - 如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解

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

4. 注解和反射机制

前面经过反编译后,我们知道Java所有的注解都继承Annotation接口,也就是说Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 JVM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。
下面是AnnotatedElement中相关的API方法,以上5个类都实现以下的方法

返回值

方法名称

说明

<A extends Annotation>

getAnnotation(Class<A> annotationClass)

该元素如果存在指定类型的注解,则返回这些注解,否则返回null

Annotation[]

getAnnotations()

该元素如果存在指定类型的注解,则返回这些注解,否则返回null

boolean

isAnnotationPresent(Class<? extends Annotation> annotationClass)

如果指定类型的注解存在此元素上,则返回true,否则返回false

Annotation[]

getDeclaredAnnotations()

返回直接存在于此元素上的所有注解,注意,不包括父类的注解

简单案例演示如下:

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

//没有使用@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {
}

@DocumentA
class testA{

}
//继承了A类
@DocumentB
public class DocumentDemo extends testA {
    public static void main(String[] args) {
        //获取指定类型的注解
        DocumentA annotation = testA.class.getAnnotation(DocumentA.class);
        System.out.println(annotation);

        //获取所有的注解,包括从父类继承
        Annotation[] annotation1 = DocumentDemo.class.getAnnotations();
        System.out.println(Arrays.toString(annotation1));

        //获取所有的注解,不包括从父类继承
        Annotation[] annotation2 = DocumentDemo.class.getDeclaredAnnotations();
        System.out.println(Arrays.toString(annotation2));
        //判断注解DocumentA是否在该元素上
        boolean b=DocumentDemo.class.isAnnotationPresent(DocumentA.class);
        System.out.println(b);
    }

}

测试结果:

java 注解 方法 value java @注解_编译器_05