1. 概念
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解主要有如下三个作用:
- 编写文档:通过代码里标识的元数据生成文档,如使用
javadoc命令生成文档doc文档 - 代码分析:通过代码里标识的元数据对代码进行分析,如使用反射
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查,如
@Override
简单来说,注解就是给计算机看的一种用于说明程序的东西,主要是起到一种标识的作用。
2. 分类
按运行机制分类:
- 源码注解
- 编译时注解
- 运行时注解
按来源分类:
- Java内置注解
- 第三方注解
- 自定义注解
3. Java内置注解
Java本身定义了7个注解,其中@Override、@Deprecated和@SuppressWarnings位于java.lang中,下面称为内置注解;另外4个位于java.lang.annotation中,将其称为元注解。
2.1 内置注解
@Override:这个注解在前面类的使用中已经见过很多次了,例如,如果想要重写父类中定义的方法,子类重写的方法上就可以添加@Override;以及任何类重写Object类中的toString()、equals()等方法时,IDEA都会自动的在方法上添加注解。因此,它主要用于编译时检查添加注解的方法是否是重写方法,当它的父类或实现的接口中没有此方法时,编译就会自动报错java @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }@Deprecated:它用于标记过时的方法,注意标记为过时的方法,只是说它已经有了更好替代,方法仍可用只是不推荐。例如Date类中就存在大量被@Deprecated标记的方法,因为Calendar类中有其对应的替代方法。java @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }@SuppressWarnings:它用于指示编译器去忽略注解中声明的警告。例如,当类中定义的方法没有使用时,编辑器会自动显式警告,如果想要忽略警告信息们可以使用该注解java @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
使用示例:
@SuppressWarnings("all")public class AnnotationTest {@Overridepublic String toString() {return "AnnotationTest{}";}@Deprecatedpublic void show(){}// show()的更新版public void newShow(){}}
2.2 元注解
元注解和所有的元xx含义是类似的,它就是用来注解其他注解的注解。Java内置了如下4个元注解:
@Retention:它用来标识注解应该保存于Java程序三个阶段的哪一个中,只在源代码中、写入.class文件中还是运行时通过反射访问。@Retention的源码实现为:```java package java.lang.annotation;
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
<br />它只有一个成员变量`value()`,类型为`RetentionPolicy`。我们继续看一下`RetentionPolicy`的源码:```javapackage java.lang.annotation;public enum RetentionPolicy {// Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息SOURCE,// 编译器将Annotation存储于类对应的.class文件中。默认行为CLASS,// 编译器将Annotation存储于class文件中,并且在运行时可由JVM读入RUNTIME}
从源码中可以看出,RetentionPolicy是一个枚举(enum)类型,其中包含有SOURCE、CLASS和RUNTIME三个值,它们分别对应了Java程序的三个阶段
@Documented:它用来标记注解是否应包含到文档中,主要用于使用javadoc生成文档时是否会显示注解
注解的源码实现为:```java package java.lang.annotation;
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
<br />它本身没有包含成员变量,一般在使用中它可有可无,只有在有需要生成javadoc文档时有用。- `@Target`:它用来标记**注解应是那种Java成员**,注解的源码实现如下:```java@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target {ElementType[] value();}
可以看到@Target中只有一个类型为ElementType[]类型的成员变量value(),接下来再看一下ElementType的源码:```java
public enum ElementType {
TYPE,
FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USE
}
<br />`ElementType`同样是一个枚举类型,其中包含10个值,它们分别用于标记注解能用来修饰什么类型的成员:
- TYPE:类、接口或枚举声明
- FIELD:字段声明
- METHOD:方法声明
- PARAMETER:参数生命
- CONSTRUCTOR:构造方法声明
- LOCAL_VARIABLE:局部变量声明
- ANNOTATION_TYPE:注解类型声明
- PACKAGE:包声明
- TYPE_PARAMETER:Type参数声明
- TYPE_USE:类型使用声明
- `@Inherited`:它用来标记**注解是继承自哪个注解类**,源码实现为:```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Inherited的实现中同样没有成员变量,只是起来一个简单的标识作用。
4. 自定义注解
从前面Java内置注解的源码实现中,我们对于注解的定义有了初步的感受。如果用户想要自定义注解,注解的通用定义一般如下所示:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 成员变量
}
注意事项:
@interface是定义注解的关键字,当使用它定义注解时,表示该注解实现了java.lang.annotation.Annotation接口,接口的实现由编译器完成,同时该注解不能再继承其他注解或接口- 成员变量的类型是有限的,合法的类型包括:
- 八大基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
- 当注解中只有一个成员变量时,变量名必须是
value(),注解使用时可以忽略成员名和= - 没有成员变量的注解称为标识注解
- 可以使用
default关键字给成员变量设置默认初始化值,使用注解时就可以不对成员变量赋值 - 注解使用时,如果成员变量是数组,那么赋值时使用
{}包含,如果只有一个值,则可忽略{}
假设我们定义一个注解@Description,它只有一个String类型的成员变量value()。
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String value();
}
然后定义一个接口,接口中有一些抽象方法
public interface People {
String name();
int age();
void work();
}
接着定义接口的实现类,并在类中使用自定义的注解
@Description("class annotation...")
public class Employee implements People{
@Override
public String name() {
return null;
}
@Override
@Description("method annotation...")
public int age() {
return 0;
}
@Override
public void work() {
}
}
由于注解中只有一个String类型的成员变量,因此这里简单的传入一个字符串,用来标识该注解用在哪里。定义好类之后,我们需要创建一个包含main()的类来使用Employee,定义如下:
import java.lang.reflect.Method;
public class AnnotationDemo {
public static void main(String[] args) throws Exception{
// 获取Class类对象
Class<?> aClass = Class.forName("Annotation.Employee");
// 判断类是否使用了注解
if (aClass.isAnnotationPresent(Description.class)){
// 获取使用的注解实例
Description annotation = aClass.getAnnotation(Description.class);
System.out.println(annotation.value()); //class annotation...
}
// 获取类对象的成员方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
// 判断方法是否使用了注解
if (method.isAnnotationPresent(Description.class)){
// 获取使用的注解实例
Description annotation = method.getAnnotation(Description.class);
System.out.println(annotation.value()); // method annotation...
}
}
}
}
5. 使用案例
在前面反射的部分,我们使用了反射来运行配置文件内容,同样的功能也可以使用今天学习的注解来实现。首先定义Porp注解,其中包含className()和methodName()两个String类型的成员变量,它和前面的配置文件是对应的。
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Prop {
String className();
String methodName();
}
然后使用同样的Person类:
package Annotation;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", school='" + school + '\'' +
'}';
}
public void say(){
System.out.println("say hello...");
}
}
最后编写测试类:
import java.lang.reflect.Method;
@Prop(className = "Annotation.Person", methodName = "say")
public class PropDemo {
public static void main(String[] args) throws Exception {
// 获取Class类对象
Class<PropDemo> c = PropDemo.class;
Prop annotation = c.getAnnotation(Prop.class);
String className = annotation.className();
String methodName = annotation.methodName();
Class<?> aClass = Class.forName(className);
// 获取方法对象
Method method = aClass.getMethod(methodName);
// 创建对象
Object o = aClass.newInstance();
// 执行方法
method.invoke(o); // say hello...
}
}
可以看出使用注解不仅可以实现同样的功能,而且实现更加的优雅,代码书写更简洁。

