Java高级特性 第6节 注解初识

一、注解概述

  Java注解也就是Annotation,是Java代码里的特殊标记,它为Java程序代码提供了一种形式化的方法,用来表达额外的某些信息,这些信息是代码本身无法表示的。

  注解以标签的形式存在于代码之中,注解的存在并不影响代码程序的编译执行,它只是用来生成其他的文件或使我们在运行代码时知道被运行代码的描述信息。

  注解的语法格式:@Annotation(参数)

  • 将注解置于所有修饰符之前;
  • 通常将注解单独放在一行;
  • 默认情况下,注解可以用于修饰任何程序元素,包括类、方法和成员变量等;

二、注解分类

  Java中根据注解的作用和使用方法,可以分为3类:内建注解、元注解、自定义注解。

  注解很重要,未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,现在已经有不少的人开始用注解了。

  1. 内建注解

  注解是JDK1.5之后才有的新特性,JDK1.5版本的java.lang包下提供了3中标准的注解类型:

  • @Override  被用来标注方法,表示该方法是重写父类的某方法;
//源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • @Deprecated   标识程序元素已过时、废弃,编译器将不再鼓励使用这个被标注的元素
//源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
  • @SuppressWarnings 标识阻止编译器警告,被用于有选择性的关闭编译器对类、方法和成员变量等程序元素及其子元素的警告。此注解会一直作用于该程序的所有子元素;
//源码
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

   @SuppressWarnings注解内部有一个String数组,主要接收值:

    • deprecation 使用了过时的程序元素
    • unchecked  执行了未检查的转换
    • unused  有程序元素未被使用
    • fallthrough  switch程序块直接通往下一种情况而没有break
    • path  在类路径、源文件路径等中有不存在的路径
    • seial  在可序列化的类上缺少serialVersionUID定义
    • finally  任何finally子句不能正常完成
    • all  所有情况 
package cn.yu.annotation;
/**
 * JDK1.5内部提供的三种注解是:@SuppressWarnings(":deprecation")、@Deprecated、@Override
 * @author yu
 */
public class AnnotationTest {
    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        System.runFinalizersOnExit(true);//这里的runFinalizersOnExit()方法画了一条横线表示此方法已经过时了,不建议使用了
    }
    @Deprecated //这也是JDK内部自带的一个注解,意思就是说这个方法已经废弃了,不建议使用了
    public static void sayHello(){
        System.out.println("hi,yyyy");
    }
    @Override //这也是JDK1.5之后内部提供的一个注解,意思就是要重写(覆盖)JDK内部的toString()方法
    public String toString(){
        return "yyyyy";
    }
}

  注意:

    • 当注解类型中只有一个value成员变量,使用该注解时可以直接在注解后的括号里指定value成员变量的值,而无需使用name=value结构对的形式。例如在@SuppressWarnings注解类型中只有一个value成员变量,则可以把“value=”省略掉:@SuppressWarnings({"unchecked","fallthrough"});
    • 若@SuppressWarnings注解所在声明的被禁止的警告个数只有一个时,则可以不用大括号:@SuppressWarnings("unchecked");

  2. 元注解

  java.lang.annotation包下提供了4个元注解,他们用来修饰其他的注解定义。

  • @Target注解

   @Target元注解决定了一个注解可以标识到哪些成分上,如标识在在类身上,或者属性身上,或者方法身上等。@Target注解类型由唯一的value作为成员变量。这个成员变量是java.lang.annotation.ElementType类型,ElementType类型是可以被标注的程序元素的枚举类型。@Target的成员变量value为如下值时,则可指定被修饰的注解只能按如下声明进行标注,当value为FIELD时,被修饰的注解只能用来修饰成员变量。

public enum ElementType {
    /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
    TYPE,
    /** 标明该注解可以用于字段(域)声明,包括enum实例 */
    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})
  • @Rentation注解

   @Rentation注解描述了被其修饰的注解是否北边一起丢弃或者保留在class文件中。默认情况下,注解被保存在class文件中,但在运行时不能反射访问。

   @Retention用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime),其含有如下:

    • SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里);
    • CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等;
    • RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
/*
*通过将value成员变量的值设为RetentionPolicy.RUNTIME,指定@Retention注解在运行时可以通过反射进行访问
*/
@Target(ElementType.ANNOTATION_TYPE ) @Retention(RetentionPolicy.RUNTIME)//保存到运行时 public @interface DBTable { String name() default "yu"; }
  • @Documented注解  

   用于指定被其修饰的注解将被JavaDoc工具提取成文档,如果再定义某注解时使用了@Documented修饰,则所有使用该注解修饰的程序元素的API文档中都将包含该注解说明。另外,@Documented注解类型没有成员变量。

//定义注解
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentOne {

}
//使用注解
@DocumentOne 
public class DocumentTest{
    public void methodA(){
         //....
    }
}
  • @Inherited注解  

  用于指定被其修饰的注解将具有继承性,也就是说,如果一个使用了@Inherited注解修饰的注解被用于某个类,则这个注解也将被用于该类的子类(这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解)。

package cn.SynchronizationDemo;
import java.lang.annotation.*;
import java.util.Arrays;

@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DocumentOne{

}
@DocumentOne
class ClassA{ }
class ClassB extends ClassA{ }
//测试
public class DocumentTest {
    public static void main(String[] args) {
        ClassA cla = new ClassB();
        System.out.println("使用@Inherited注解:" + Arrays.toString(cla.getClass().getAnnotations()));

    }
}

  

  3. 自定义注解

  注解类型是一种接口,但它有不同于接口。定义一个新的注解类型与定义一个接口非常类似,定义新的注解要使用@interface关键字:

public  @interface  AnnotationTest{
}

  注解定义好后,就可以用来修饰程序中的类、接口、方法和成员变量等。

package cn.AnnotationDemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * MetaAnnotation注解类为自定义元注解
 * @author yu
 */
@interface MetaAnnotation {
    String value();//元注解MetaAnnotation设置有一个唯一的属性value
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
@interface MyAnnotation {
    String color() default "black";
    String value();
    int[] arrayAttr() default {1,2,4};
    //枚举类型
    enum EumTrafficLamp {RED,YELLOW,GREEN,BLUE};
    EumTrafficLamp lamp() default EumTrafficLamp.RED;
    //为注解添加一个注解类型的属性,并指定注解属性的缺省值
    MetaAnnotation annotationAttr() default @MetaAnnotation("xjdkf");
}
//这里是将新创建好的注解类MyAnnotation标记到AnnotaionTest类上
@MyAnnotation(color = "red",value = "yu",arrayAttr = {2,4,5},lamp = MyAnnotation.EumTrafficLamp.GREEN,annotationAttr = @MetaAnnotation("sdssss"))
public class AnnotationUse {
    public static void main(String[] args) {
        // 检查Annotation类是否有注解,这里需要使用反射才能完成对Annotation类的检查
        if (AnnotationUse.class.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation annotation = (MyAnnotation) AnnotationUse.class.getAnnotation(MyAnnotation.class);
            System.out.println(annotation.color());
            System.out.println(annotation.value());
            for (int value:annotation.arrayAttr()) {
                System.out.println(value);
            }
            MyAnnotation.EumTrafficLamp eumTrafficLamp = annotation.lamp();
            if(annotation.lamp().equals(eumTrafficLamp.GREEN)) {
                System.out.println(annotation.lamp());
            }
            MetaAnnotation ma = annotation.annotationAttr();//annotation是MyAnnotation类的一个实例对象
            System.out.println(ma.value());//输出的结果为:sdssss
        }
    }
}

  

  4. 读取注解信息

  java.lang.reflect包主要包含一些实现反射功能的工具类,另外也提供了对读取运行时注解的支持。java.lang.reflect包下的AnnotationElement接口代表程序中可以接受注解的程序元素,该接口有以下几个实现类:

  • Class:类的Class对象定义   
  • Constructor:代表类的构造器定义   
  • Field:代表类的成员变量定义 
  • Method:代表类的方法定义   
  • Package:代表类的包定义

  java.lang.reflect.AnnotatedElement中相关的API方法如下:

  

package cn.AnnotationDemo;
import java.lang.annotation.Annotation;
import java.lang.annotation.*;
import java.util.Arrays;

@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DocumentOne {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DocumentTwo {
}

@DocumentOne
class A{ }

//继承了A类
@DocumentTwo
public class DocumentDemo extends A{
public static void main(String[] args){
Class<?> clazz = DocumentDemo.class;
//根据指定注解类型获取该注解
DocumentOne documentA=clazz.getAnnotation(DocumentOne.class);
System.out.println("A:"+documentA);

//获取该元素上的所有注解,包含从父类继承
Annotation[] an= clazz.getAnnotations();
System.out.println("an:"+ Arrays.toString(an));

//获取该元素上的所有注解,但不包含继承!
Annotation[] an2=clazz.getDeclaredAnnotations();
System.out.println("an2:"+ Arrays.toString(an2));

//判断注解DocumentA是否在该元素上
boolean b=clazz.isAnnotationPresent(DocumentOne.class);
System.out.println("b:"+b);
}
}

  

  注意:

  这里得到的注解,都是被定义为运行时的注解,即是用@Retention(RetentionPolicy.RUNTIME)修饰的注解。否则,通过反射得不到这个注解信息。

原文地址:https://www.cnblogs.com/yutianbao/p/10673719.html