Java 注解

来源:互联网 发布:淘宝球鞋家是正品吗 编辑:程序博客网 时间:2024/06/05 07:24

内容参考:
http://blog.csdn.net/javazejian/article/details/71860633 及其他博客。

自Java5.0版本引入注解之后,它就成为了Java平台中非常重要的一部分。

什么是注解?

用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码:

@Overridepublic String toString() {    return "This is String Representation of current object.";}

上面的代码中,重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。

Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。

更直观的理解,它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。

实际上Java注解与普通修饰符(public、static、void等)的使用方式并没有多大区别,下面的例子是常见的注解:

public class AnnotationDemo {    //@Test注解修饰方法A    @Test    public static void A(){        System.out.println("Test.....");    }    //一个方法上可以拥有多个不同的注解    @Deprecated    @SuppressWarnings("uncheck")    public static void B(){    }}

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

为什么要引入注解?

使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。如果你在Google中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。

假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。

目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。

作用

Annotation的引入是为了从Java语言层面上,为Java源代码提供元数据的支持。

  • 标记,用于告诉编译器一些信息
    Marker Annotation:该Annotation没有参数输入,更多类似于标识一个东西,类似于Java语言中的java.io.Serialable之类的接口,并无需要实现的方法。
  • 编译时动态处理,如动态生成代码
  • 运行时动态处理,如得到注解信息

Annotation是如何工作的?怎么编写自定义的Annotation?

编写Annotation非常简单,可以将Annotation的定义同接口的定义进行比较。我们来看两个例子:一个是标准的注解@Override,另一个是用户自定义注解@Todo。

@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}

其中的@interface是一个关键字,这个关键字声明隐含了一个信息:它是继承了java.lang.annotation.Annotation接口,并非声明了一个interface。在设计annotations的时候必须把一个类型定义为@interface,而不能用class或interface关键字。

对于@Override注释你可能有些疑问,它什么都没做,那它是如何检查在父类中有一个同名的函数呢。当然,不要惊讶,我是逗你玩的。@Override注解的定义不仅仅只有这么一点代码。这部分内容很重要,我不得不再次重复:Annotations仅仅是元数据,和业务逻辑无关。理解起来有点困难,但就是这样。如果Annotations不包含业务逻辑,那么必须有人来实现这些逻辑。元数据的用户来做这个事情。Annotations仅仅提供它定义的属性(类/方法/包/域)的信息。Annotations的用户(同样是一些代码)来读取这些信息并实现必要的逻辑。

当我们使用Java的标注Annotations(例如@Override)时,JVM就是一个用户,它在字节码层面工作。到这里,应用开发人员还不能控制也不能使用自定义的注解。因此,我们讲解一下如何编写自定义的Annotations。

J2SE5.0版本在 java.lang.annotation提供了四种元注解专门注解(标记)其他的注解:

@Documented –注解是否将包含在JavaDoc@Retention –什么时候使用该注解@Target? –注解用于什么地方@Inherited – 是否允许子类继承该注解

@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中(@Documented 被修饰的注解会生成到javadoc中)。

@Retention– 定义该注解的生命周期。

RetentionPolicy.SOURCE – 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。RetentionPolicy.CLASS – 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。RetentionPolicy.RUNTIME– 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

Java编译时注解和运行时注解有什么区别

1)编译时注解,注解内容只存在源文件,在编译期间将被丢弃,不能通过JVM获取注解信息;(@Override, @SuppressWarnings都属于这类注解。)

2)运行时注解,编译时被存储在.class字节码文件,可以通过JVM运行时获取注解信息(且只限于被RUNTIME注解的注解)。我们自定义的注解通常使用这种方式。

@Target – 表示该注解用于什么地方。如果不明确指出,该注解可以放在任何地方。以下是一些可用的参数。需要说明的是:属性的注解是兼容的,如果你想给7个属性都添加注解,仅仅排除一个属性,那么你需要在定义target包含所有的属性。

ElementType.TYPE:用于描述类、接口或enum声明ElementType.FIELD:用于描述实例变量ElementType.METHODElementType.PARAMETERElementType.CONSTRUCTORElementType.LOCAL_VARIABLEElementType.ANNOTATION_TYPE 另一个注释ElementType.PACKAGE 用于记录java文件的package信息

@Inherited – 定义该注释和子类的关系(@Inherited 可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解)

@Inherited@Documented@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface DocumentA {}@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface DocumentB {}@DocumentAclass A{ }class B extends A{ }@DocumentBclass C{ }class D extends C{ }//测试public class DocumentDemo {    public static void main(String... args){        A instanceA=new B();        System.out.println("已使用的@Inherited注解:"+Arrays.toString(instanceA.getClass().getAnnotations()));        C instanceC = new D();        System.out.println("没有使用的@Inherited注解:"+Arrays.toString(instanceC.getClass().getAnnotations()));    }    /**     * 运行结果:     已使用的@Inherited注解:[@com.zejian.annotationdemo.DocumentA()]     没有使用的@Inherited注解:[]     */}

那么,注解的内部到底是如何定义的呢?
Annotations只支持基本类型、String及枚举类型。注释中所有的属性被定义成方法,并允许提供默认值。

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@interface Todo {    public enum Priority {LOW, MEDIUM, HIGH}    public enum Status {STARTED, NOT_STARTED}    String author() default "Yash";    Priority priority() default Priority.LOW;    Status status() default Status.NOT_STARTED;}

下面的例子演示了如何使用上面的注解:

@Todo(priority = Todo.Priority.MEDIUM, author = "Yashwant", status = Todo.Status.STARTED)public void incompleteMethod1() {    //Some business logic is written    //But it’s not complete yet}

如果注解中只有一个属性,可以直接命名为“value”,使用时无需再标明属性名。

@interface Author{String value();}@Author("Yashwant")public void someMethod() {}

编译器对默认值的限制

编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值,这就是限制,没有什么利用可言,但造成一个元素的存在或缺失状态,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。

注解不支持继承

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

package com.zejian.annotationdemo;import java.lang.annotation.Annotation;//反编译后的代码public interface DBTable extends Annotation{    public abstract String name();}

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

但目前为止一切看起来都还不错。我们定义了自己的注解并将其应用在业务逻辑的方法上。现在我们需要写一个用户程序调用我们的注解。这里我们需要使用反射机制。如果你熟悉反射代码,就会知道反射可以提供类名、方法和实例变量对象。所有这些对象都有getAnnotation()这个方法用来返回注解信息。我们需要把这个对象转换为我们自定义的注释(使用 instanceOf()检查之后),同时也可以调用自定义注释里面的方法。看看以下的实例代码,使用了上面的注解:

Class businessLogicClass = BusinessLogic.class;for(Method method : businessLogicClass.getMethods()) {    Todo todoAnnotation = (Todo)method.getAnnotation(Todo.class);    if(todoAnnotation != null) {        System.out.println(" Method Name : " + method.getName());        System.out.println(" Author : " + todoAnnotation.author());        System.out.println(" Priority : " + todoAnnotation.priority());        System.out.println(" Status : " + todoAnnotation.status());    }}

Java内置注解与其他元注解

接着看看Java提供的内置注解,主要有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 {}

@SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings {    String[] value();}

注解与反射

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

Class:类的Class对象定义   Constructor:代表类的构造器定义   Field:代表类的成员变量定义 Method:代表类的方法定义   Package:代表类的包定义
原创粉丝点击