首页 > 代码库 > Java注解介绍

Java注解介绍

原文链接: Java Annotations: An Introduction
原文日期: 2005年10月14日
翻译日期: 2014年07月20日
翻译人员: 铁锚

翻译完后,感觉这篇文章是不是在http://www.developer.com被挖坟了?


Java注解介绍

基于注解(Annotation-based)的Java开发无疑是最新的开发趋势.[译者注: 这是05年的文章,在2014年,毫无疑问,多人合作的开发,使用注解变成很好的合作方式,相互之间的影响和耦合可以很低]. 

基于注解的开发将Java开发人员从繁琐笨重的配置文件中解脱出来. Java 5.0中首次引入了注解,注解是这个JDK版本的特性之一,将程序员书写Java示例API文件的工作转交给了编译器. 当不再维护分开的源代码和API文档后,代码和文档会更容易维护. 生成的代码示例也不太可能包含错误.
Java注解是JDK 5中的主要特性之一,使开发变得更简单容易. 注解就像一种元信息(meta,可以理解为额外的信息,用来给特殊的程序看的),可以添加到代码中,可以被用到包(package)的声明,类型(type)声明、构造函数(constructors)、方法(methods)、属性域(fields)、参数(parameters)以及变量(variables)上. 他们提供一种有效的方式来指示方法是否依赖于其他方法,是否完整,类(class)是否引用其他类,等等.
引用 甲骨文公司的官方网站的说明 ,"它(基于注解的开发)可以让我们在很多时候不必再去写一份单独的API文档说明,只需要启用工具来从源码中的注解生成即可. 这形成了一种声明式的编程风格,程序员说,需要做什么,就让工具根据代码来做就好了."
简单来说,注解是一种将meta标记(meta-tag)与程序元素关联的机制,允许编译器(compiler)或JVM从有注解的元素提取程序行为,必要时生成相互依赖的代码.
在本系列文章的第一部分,我将介绍一些基本的Java注解,他们的用处(benefits),以及一些示例用法(usages).

Java注解基础知识

你需要了解两个地方. 一是"注解"(annotation, 类似于一个 new出来的对象)本身,二是"注解的类型"(annotation type, 类似于 class定义). 注解是一个meta标记,用在你的代码中,其实它是有生命周期和适用范围的. annotation type是用来定义注解的. 当你想要创建自己的注解时,你就会用到它. type才是实际使用的构造类型,注解只是那个类型的一个具体用法.

定义注解类型时需要使用一个"at"(@,国内有人读作圈a)标记,紧跟着是关键字 interface, 再加上注解的名字(name). 另一方面,使用注解的形式,也是先写上"at"符号(@),其次是注解类型. 这是最简单的注解形式. 另外,你可以在使用注解时,在名字后面加上小括号,里面附上需要传递的参数. 后面你会看到他们的示例:

定义注解类型示例: (Annotation Type,注解类型, 类似于定义一个类)
public @interface MyAnnotation {
   String doSomething();
}

在普通代码中使用注解(Annotation实例)
@MyAnnotation (doSomething="What to do")
public void mymethod() {
   ....
}

Java注解类型(Annotation Types)

有三种类型的注解:

标记(Marker): 标记类型的注解没有元素,只有一个名字.

定义:
// 这种注解,就像一种标签, 没有状态
// 有点像没有方法定义的接口Serializable一样
public @interface AMarkerAnnotation {
}
使用:
@AMarkerAnnotation
public void mymethod() {
   ....
}

单个元素的注解: 单个元素(Single-Element)或单个值 这种类型的注解,只带一个数据. 可以在括号内用 data=http://www.mamicode.com/value 的方式表示,也可以只传递一个值(简便的写法).
定义:
public @interface SingleElementAnnotation
{
    String doSomething();
}

使用:
@SingleElementAnnotation ("可以只传对应类型的值")
public void mymethod() {
   ....
}

Full-value或多个值的注解: Full-value类型的注解有多个数据成员. 因此,必须为每个成员使用完整的 data=http://www.mamicode.com/value 语法格式传递参数.
定义:
public @interface FullValueAnnotation {
   String doSomething();
   int count; 
   String date();
}

使用:
@FullValueAnnotation (doSomething="参数值", count=1,
               date="09-09-2005")
public void mymethod() {
   ....
}

定义Java注解类型的注意事项

在定义注解类型时需要注意:

  1. 注解声明应该以一个at符号开始(@),后跟一个interface 关键字,以及注解的名字.
  2. 注解里面的方法声明,不接受任何参数(只是看起来像是方法而已,本质是属性域).
  3. 注解里面的方法声明不能有throws 子句.
  4. 注解里面的方法返回类型只能是下面这几种:
  • primitives(6种原始数据类型,int,byte等)
  • String(字符串)
  • Class(类, 如 String.class 这种)
  • enum(枚举)
  • array of the above types(数组,数组元素只能是上面的一种类型)
Java注解类型
JDK5中有两种类型的注解:
  • Simple annotations(简单注解类型): 这些是 Tiger(Tiger是JDK1.5的代号?)提供的基本类型,只能用来注解普通代码;不能用来创建另一个自定义注解类型.
  • Meta annotations(元注解): 专门设计用来注解其他注解类型(annotation-type)声明的. 简单地说,他们被称为注解的注解(annotations-of-annotations).
----------------------------------------------------------------------
三个简单Java注解

简单Java注解

JDK5提供的简单注解类型只有3个. 这三个都是用来预防错误或者进行提醒的,分别是:
  • Override
  • Deprecated
  • Suppresswarnings
需要注意,JDK5(另一个说法,Tiger)实际上并没有许多内置注解;相反,它允许核心Java支持注解特性的能力. JSR-175中严格规定它用来定义元数据功能. 需要由程序员编写自定义的注解类型,其他JSR标准也编写了一系列标准注解类型. 下面将用实例来深入讲解这三个简单注解.

Override 注解
Override 注解指明被注解的方法需要覆写超类中的方法.
如果某个方法使用了该注解,却没有覆写超类中的方法(比如大小写写错了,或者参数错了,或者是子类自己定义的方法),编译器就会生成一个错误. 
(注意: JRE5中实现接口中的方法时不能使用Override注解,JRE6允许了,很多时候JRE5会报这个错). 

示例1演示了覆写注解:
Java注解示例1
public class Test_Override {
	@Override
	public String toString() {
	   return super.toString() + "测试使用 ‘Override‘ 注解";
	}
}

如果出现方法名字拼写错误会发生什么? 例如,如果你将toString方法改名为"tostring"(全小写),编译时就会得到类似下面这样的出错信息:
Compiling 1 source file to D:tempNew Folder (2)
                              TestJavaApplication1buildclasses
D:tempNew Folder (2)TestJavaApplication1srctest
   myannotationTest_Override.java:24: method does not override
                a method from its superclass
@Override
1 error
BUILD FAILED (total time: 0 seconds)
当然,Eclipse就会直接报红叉.现在IDE发展的很好用,初学者不应该去折腾JDK的命令行了.

Deprecated 注解
这个注解表明如果程序调用一个废弃的(Deprecated,废弃的,过时的)元素时,编译器应该显示警告信息. 示例2显示了如何使用Deprecated 注解.

Java注解示例2
首先,创建一个类,并像下面这样将某个方法标明为 废弃:
public class Test_Deprecated {
   @Deprecated
   public void doSomething() {
      System.out.println("测试使用 弃用 注解: ‘Deprecated‘");
   }
}

接着,尝试从另一个类调用这个方法:
public class TestAnnotations {
   public static void main(String arg[]) throws Exception {
      new TestAnnotations(); 
   }
   public TestAnnotations() {
   Test_Deprecated t2=new Test_Deprecated();
   t2.doSomething();
}

本例中的doSomething()方法被声明为废弃的方法. 因此,一般情况下不应该调用这个方法. 在编译Test_Deprecated.java 文件时是不会有警告消息的. 但在编译 TestAnnotations.java 时编译器就会给出类似这样的警告信息(Eclipse 会有警告):
Compiling 1 source file to D:tempNew Folder
(2)TestJavaApplication1buildclasses
D:tempNew Folder
(2)TestJavaApplication1srctestmyannotation
    TestAnnotations.java:27:
warning: [deprecation] doSomething() in
test.myannotation.Test_Deprecated has been deprecated
t2.doSomething();
1 warning

Suppresswarnings 注解
这个注解告诉编译器应该屏蔽带注解的元素和所有子元素的警告信息. 会压制一个元素集和子元素的所有警告信息. 比如,假设你在一个class上使用了Suppresswarnings 注解压住一个警告,在它的一个方法上用Suppresswarnings 注解来压制另一个警告,则两种警告都会在方法级别被压制住. 请参见示例3.

Java注解示例3
public class TestAnnotations {
   public static void main(String arg[]) throws Exception {
      new TestAnnotations().doSomeTestNow();
   }
   @SuppressWarnings({"deprecation"})
   public void doSomeTestNow() {
      Test_Deprecated t2 = new Test_Deprecated();
      t2.doSomething();
   }
}
在本例中,使用 @SuppressWarnings压住了示例2中所示的deprecation警告信息. 因为该方法的这类警告被压住了,所以你不会再看到"deprecation"警告.
注意: 在最内层的元素上使用该注解是比较好的. 因此,如果你只想在一个特定的方法上压制一个警告,你应该在方法上标注,而不是在类上使用注解.

元注解(Meta-Annotations,Java注解类型)
元注解,实际上被称为注解的注解,包含四种类型. 分别是:
  • Target
  • Retention
  • Documented
  • Inherited
Target 注解
Target注解表明注解类型适用于哪种目标元素. 它包含下面的枚举类型值:

  • @Target(ElementType.TYPE) —— 可以适用于任何类的元素
  • @Target(ElementType.FIELD) —— 只适用于字段或属性
  • @Target(ElementType.METHOD) —— 只适用于方法的注解
  • @Target(ElementType.PARAMETER) —— 只适用于方法的参数
  • @Target(ElementType.CONSTRUCTOR) —— 只适用于构造函数
  • @Target(ElementType.LOCAL_VARIABLE) —— 只适用于局部变量
  • @Target(ElementType.ANNOTATION_TYPE) —— 指明声明类型本身是一个注解类型
示例4演示了Target 注解:

Java注解示例4
首先,定义了一个名为Test_Target的注解类型,带上 @Target元注解,如下所示:
@Target(ElementType.METHOD)
public @interface Test_Target {
   public String doTestTarget();
}

接下来,创建一个类,它将使用Test_Target注解:
public class TestAnnotations {
   public static void main(String arg[]) {
      new TestAnnotations().doTestTarget();
   }
   // 在方法上使用注解,OK.
   // 中间也可以不换行,换2行之类,Java忽略多余的换行
   @Test_Target(doTestTarget="Hello World !")
   public void doTestTarget() {
      System.out.printf("Testing Target annotation");
   }
}

@Target(ElementType.METHOD) 注解表明该注解类型只能被用来注解方法. 如果你编译这段代码,不会显示警告消息. 但是,如果将这个注解声明到一个字符串变量上,会发生什么呢? 就像下面这样:
public class TestAnnotations {
   // 这是错误的做法,编译不会通过,因为注解的Level不对.
   // 元注解指明了只能注解方法,就不能用来注解属性
   @Test_Target(doTestTarget="Hello World !")
   private String str;
   public static void main(String arg[]) {
      new TestAnnotations().doTestTarget();
   }
   public void doTestTarget() {
      System.out.printf("Testing Target annotation");
   }
}

唯一的变化就是注解声明从方法级转向字段级,这是不正确的. 因为你已经定义了注解   @Test_Target 只适用在方法级,如果你尝试编译这个类,你可能会得到这样的错误信息:
"TestAnnotations.java": 
D:R_AND_DTestAnnotationsrctestmyannotation
   TestAnnotations.java:16: 
annotation type not applicable to this kind of declaration at line
16, column 0
@Test_Target(doTestTarget="Hello World !")
^
Error in javac compilation

------------------------------------------------------------------------------------------

介绍Java注解

Retention注解

Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值:
  • RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
  • RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
  • RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
示例5演示了 RetentionPolicy.RUNTIME 的声明:

Java注解的示例5
@Retention(RetentionPolicy.RUNTIME)
public @interface Test_Retention {
   String doTestRetention();
}

在这个示例中, @Retention(RetentionPolicy.RUNTIME)注解表明 Test_Retention注解将会由虚拟机保留,以便它可以在运行时通过反射读取.

Documented 注解
Documented 注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中. 示例6进一步演示了使用 @Documented:

Java注解的示例6
@Documented
public @interface Test_Documented {
   String doTestDocument();
}

接下来,像下面这样修改TestAnnotations类:
public class TestAnnotations {
   public static void main(String arg[]) {
      new TestAnnotations().doSomeTestRetention();
      new TestAnnotations().doSomeTestDocumented();
   }
   @Test_Retention (doTestRetention="保留注解信息测试")
   public void doSomeTestRetention() {
      System.out.printf("测试注解类型 ‘Retention‘");
   }
   @Test_Documented(doTestDocument="Hello document")
   public void doSomeTestDocumented() {
      System.out.printf("测试注解类型 ‘Documented‘");
   }
}

现在,如果你使用 javadoc命令生成 TestAnnotations.html文件,你将看到类似于图1的结果.

图1

从截图可以看到,文档中没有 doSomeTestRetention() 方法的 annotation-type信息()方法. 但是, doSomeTestDocumented() 方法的文档提供了注解的描述信息. 这是因为 @Documented标签被加到了Test_Documented注解上. 之前的注解Test_Retention并没有指定 @Documented 标记(tag).

Inherited 注解(这段可能有问题...)
这是一个稍微复杂的注解类型. 它指明被注解的类会自动继承. 更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中. 在示例7中,你会看到使用 @Inherited 标签的好处.

Java注解的示例7
首先,定义你的注解:
@Inherited
public @interface MyParentObject { 
      boolean isInherited() default true;
      String doSomething() default "Do what?";
}
接下来,使用注解标注了一个类:
@MyParentObject
public Class MyChildObject {
}
正如你看到的,你不需要在实现类中定义接口方法. 因为使用 @Inherited标记,这些都自动继承了. 如果你使用一种古老的方式定义实现类,会是什么样子呢? 看看下面这张 古老的实现方式吧:
public class MyChildObject implements MyParentObject {
   public boolean isInherited() {
      return false;
   }
   public String doSomething() {
      return "";
   }
   public boolean equals(Object obj) {
      return false;
   }
   public int hashCode() {
      return 0;
   }
   public String toString() {
      return "";
   }
   public Class annotationType() {
      return null;
   }
}

看到的区别吗? 可以看到,你必须实现父接口的所有方法. 除了isInherited()和从myParentObject doSomething()方法外,你还需要实现 java.lang.Object的 equals(),toString()和hasCode()方法. 还有 java.lang.annotation.Annotation 类的 annotationType()方法. 不管你是不是想要实现这些方法,你必须在继承的对象中包含这些.

结论
本文向你展示了如何通过使用JDK5的注解功能使开发更容易. 注解不直接影响程序的语义. 开发和部署工具可以以某种方式阅读这些注解并处理它们,使用包含注解的程序可以替代额外的Java源文件、XML文档或其他古老的构件.  使用注解可以使用更少的代码完成同样的事情,并且有更好的编译时错误检测机制. 注解的目的是花更少的时间在那些死硬无用的细节中,更多地关注业务逻辑规则. 本文是Java注解系列的第一部分. 在第二部分中,你将了解如何使用注解来开发一个简单的Web应用程序. 最后,在第三部分中,你会看到一个包括多个数据库表的复杂示例.

Java注解相关的资源
  1. J2SE 5.0新特性和增强功能 
  2. J2SE 5.0简介
  3. Tiger的注解,第1部分:元数据添加到Java代码中  
  4. Java 1.5中有什么新特性? 
  5. NetBeans4中的老虎 
  6. 驯服老虎.pdf 

Java注解介绍