Java注解 Annotation

Java 注解 Annotation

 

Annotation 的由来

  从 JDK5.0 发布以来,5.0 平台提供了一个正式的 annotation 功能:允许开发者定义、使用自己的 annotation 类型。

  此功能由一个定义 annotation 类型的语法和一个描述 annotation 声明的语法,读取 annotation 的 API,一个使用 annotation 修饰的 class 文件,一个 annotation 处理工具(apt)组成。

 

Annotation 工作方式

  Annotation并不直接影响代码语义,但是它能够被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响。

  Annotation可以从源文件、class文件、或者以在运行时反射的多种方式被读取。

 

JDK 中内置的注解

  Java 注解 Annotation:

  Override注解(@Override)表示子类要重写父类的对应方法。

  Deprecated注解(@Deprecated)表示方法是不建议被使用的。

  SuppressWarnings注解(如:@SuppressWarnings("unchecked"))表示抑制警告。

  括号中的参数(一个字符串数组)表示要压制的警告类型。单个字符串的时候可以用也可以不用花括号,但是多个字符串的时候需要用花括号包起来表示,

  比如:    

  @SuppressWarnings({"unchecked","deprecation"})

  如果注解一个类去压制一种警告,再注解类中的方法取压制另一种警告,则方法中会同时压制这两种警告。

 

Annotation 的定义方式

  自己定义注解:

  新建 ->Annotation.

  可以看到注解定义的形式与 interface 类似,不同的是在 interface 关键字前面加了一个 @符号。

  自定义注解:当注解中的属性名为 value 时,在对其赋值时可以不指定属性的名称而直接写上属性值即可;除了 value 以外的其他值都需要使用 name=value 这种显式赋值方式,即明确指定给谁赋值。

  程序示例 1  

注解程序示例 1——自定义注解
public @interface AnnotationTest
{
String value() </span><span style="color: rgba(0, 0, 255, 1)">default</span> "hello"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">不加()就出错了
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">属性名字叫作value时可以不加名字直接赋值
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">属性名字不叫作value时给属性赋值必须显式指定名字
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">可以通过default关键字设置默认值</span>

}

 

注解程序示例 1——使用自定义注解
//使用自定义的注解:
@AnnotationTest(value = "hello")
// 自定义注解中加了 String value 属性之后需要对属性进行赋值,当属性有默认值之后可以不赋值
public class AnnotationUsage
{
    // 使用自定义的注解:
    @AnnotationTest("world")
    // 不加 value= 也是可以的
    public void method()
    {
        System.out.println("usage of annotation!");}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args)
{
    AnnotationUsage usage </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AnnotationUsage();
    usage.method();
}

}

 

  可以使用关键字default给属性设定默认值,如:String value() default "hello";

  程序示例 2  

注解程序示例 2
public @interface AnnotationTest
{
String[] value1() </span><span style="color: rgba(0, 0, 255, 1)">default</span> "hello"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不加()就出错了
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 属性名字叫作value时可以不加名字直接赋值
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 属性名字不叫作value时给属性赋值必须显式指定名字
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可以通过default关键字设置默认值</span>
EnumTest value2();}

enum EnumTest
{
Hello, World, Welcome;
}
//使用自定义的注解:
@AnnotationTest(value2 = EnumTest.Hello)
// 自定义注解中加了属性之后需要对属性进行赋值,当属性有默认值之后可以不赋值
public class AnnotationUsage
{
// 使用自定义的注解:
@AnnotationTest(value1={"Hello","world"}, value2 = EnumTest.World)
//value1 现在是数组,所以可以赋多个值
public void method()
{
System.out.println(
"usage of annotation!");
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args)
{
    AnnotationUsage usage </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AnnotationUsage();
    usage.method();
}

}

 

  使用 @interface 自行定义注解时,实际上是自动继承了java.lang.annotation.Annotation 接口,由编译程序自动为您完成其他产生的细节。

  在定义Annotation型态时,不能继承其他的 Annotation型态或是接口。

  要想定义注解只能通过@interface关键字来定义,手动继承 Annotation 接口并不能定义一个注解类型,即如果定义一个接口继承了 Annotation,那么该接口仍然只是一个接口而不是注解。注意 Annotation 接口本身并不是注解。

  定义 Annotation 型态时也可以使用包来管理类别,方式类同于类的导入功能。

 

告知编译程序如何处理 @Retention

  java.lang.annotation.Retention型态可以在您定义 Annotation 型态时,指示编译程序该如何对待您自定义的 Annotation 型态。

  预设上编译程序会将 Annotation信息留在.class档案中,但不被虚拟机读取,而仅用于编译程序或工具程序运行时提供信息。

  在使用 Retention 型态时,需要提供

  java.lang.annotation.RetentionPolicy 的枚举型态:

  

package java.lang.annotation;

public enum RetentionPolicy
{
SOURCE,
//编译程序处理完 Annotation 信息后就完成任务,注解只会存在于源文件当中,编译器将其丢弃,不会把注解编译到 class 文件当中,

CLASS,
//编译程序将 Annotation 存储于 class 档中,缺省,即默认情况是这种行为

RUNTIME
//编译程序将 Annotation 存储于 class 档中,可由 VM 读入,可以通过反射的方式读取到

}

 

  RetentionPolicy 为 SOURCE 的例子是 @SuppressWarnings。

  仅在编译时期告知编译程序来抑制警告,所以不必将这个信息存储于.class 档案。

 

  RetentionPolicy 为 RUNTIME 的时机,可以像是您使用 Java 设计一个程序代码分析工具,您必须让 VM 能读出 Annotation 信息,以便在分析程序时使用。搭配反射(Reflection)机制,就可以达到这个目的。

  java.lang.reflect.AnnotatedElement接口。其中包含了这样几个方法:

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

    Annotation[] getAnnotations()

    Annotation[] getDeclaredAnnotations()

    boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

  Class, Constructor, Method, Field, Package等类别,都实现了 AnnotatedElement接口

  程序示例 3——通过反射获取注解信息

  首先自定义注解类型: 

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

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation
{
String hello()
default "shengqishi";
String world();

}

  之后在类中使用自定义的注解:

@MyAnnotation(hello="beijing",world="tianjin")
public class MyTest
{
@MyAnnotation(hello</span>="shanghai",world="guangzhou"<span style="color: rgba(0, 0, 0, 1)">)
@Deprecated
@SuppressWarnings(</span>"unchecked"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> output()
{
    System.out.println(</span>"Output something."<span style="color: rgba(0, 0, 0, 1)">);
}

}

  通过反射获取注解信息:  

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

//通过反射获取注解信息
public class MyReflection
{

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> main(String[] args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception
{
    MyTest myTest </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyTest();

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取Class对象</span>
    Class&lt;MyTest&gt; c = MyTest.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取Method对象</span>
    Method method = c.getMethod("output", <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Class[] {});

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 判断是否存在指定类型的注解</span>
    <span style="color: rgba(0, 0, 255, 1)">if</span> (method.isAnnotationPresent(MyAnnotation.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">))
    {
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果存在该类型注解,这执行这个方法</span>
        method.invoke(myTest, <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Object[] {});

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果MyAnnotation前面是@Retention(RetentionPolicy.RUNTIME),则执行
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果MyAnnotation前面是@Retention(RetentionPolicy.CLASS),或SOURCE,则括号内语句不执行
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 因为只有为RUNTIME时,注解信息会被读取过来,其他两种情况注解不能被反射读取过来

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回注解</span>
        MyAnnotation myAnnotation =<span style="color: rgba(0, 0, 0, 1)"> method
                .getAnnotation(MyAnnotation.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取并打印注解中的信息</span>
        String hello =<span style="color: rgba(0, 0, 0, 1)"> myAnnotation.hello();
        String world </span>=<span style="color: rgba(0, 0, 0, 1)"> myAnnotation.world();
        System.out.println(hello </span>+ ", " +<span style="color: rgba(0, 0, 0, 1)"> world);
    }

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取方法的全部的Annotation</span>
    Annotation[] annotations =<span style="color: rgba(0, 0, 0, 1)"> method.getAnnotations();
    </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (Annotation annotation : annotations)
    {
        System.out.println(annotation.annotationType().getName());
    }
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 运行结果为:
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> com.learnjava.annotation.MyAnnotation
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> java.lang.Deprecated
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 因为只能获取到RetentionPolicy为RUNTIME的注解</span>
} }

 

 

限定 annotation 使用对象 @Target

  使用java.lang.annotation.Target可以定义其使用之时机。即表示注解可以修饰什么(修饰类、方法、注解等)。

  在定义时要指定java.lang.annotation.ElementType的枚举值之一。

  

package java.lang.annotation;

public enum ElementType
{
TYPE,
//适用 class,interface,enum

FIELD,
//适用 field

METHOD,
//适用 method

PARAMETER,
//适用 method 上之 parameter

CONSTRUCTOR,
//适用 constructor

LOCAL_VARIABLE,
//适用局部变量

 ANNOTATION_TYPE,
//适用 annotation 型态

 PACKAGE,
//使用 package
}

 

  程序示例 4——设定注解目标  

注解 Target 设定示例
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
public @interface MyTarget
{
String value();

}

public class MyTargetTest
{
@MyTarget(
"hello")
// 此注解只能被用于修饰方法,放在别的地方会出错
public void doSomething()
{
System.out.println(
"Do something!");
}

}

  

要求为 API 文件 @Documented

  想要在使用者制作 JavaDoc 文件的同时,也一并将 Annotation 的讯息加入 API 文件中,使用

  java.lang.annotation.Documented

  程序示例 5——注解加入文档 

加入文档的注解
import java.lang.annotation.Documented;

@Documented
// 表示注解 DocumentedAnnotation 将会生成到文档里面去
public @interface DocumentedAnnotation
{
String hello();

}

public class DocumentedTest
{
@DocumentedAnnotation(hello
="welcome")
public void method()
{
System.out.println(
"Hello World!");
}

}

  

子类是否继承父类 @Inherited

  缺省状态下,父类中的 annotation 并不会被子类继承。

  可以在定义 Annotation 型态时加上java.lang.annotation.Inherited型态的 Annotation。

  比较简单,不再举例。

 

参考资料

  张龙老师 Java SE 视频教程。

  另:官方文档是最好的参考资料。