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 {
@Override
public String toString() {
return "AnnotationTest{}";
}
@Deprecated
public 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`的源码:```java
package 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...
}
}
可以看出使用注解不仅可以实现同样的功能,而且实现更加的优雅,代码书写更简洁。