用一个词就能形容注解,那就是元数据。即一种描述数据的数据。注解是一种应用于类,方法,参数,变量,构造器以及包声明中的特殊字符。
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
(如:重写父类方法的@Override,方法过期的@Deprecated,忽略错误的@SuppressWarings)
值得注意的是,注解不是代码本身的一部分。
使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。
另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无参的方法”来定义,其方法名定义了成员变量的名字,返回值定义了成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation{
int id();
//default 为默认值
String name() default “”;
}
@TestAnnotation(id=1,name="aa")
class Test{
}
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型(不能是包装类)外加 类、接口、注解及它们的数组。
元注解是指注解的注解,包括@Retention @Target @Document @Inherit四种
(一)@Retention(保留期):定义注解的保留策略
@Retention(RetentionPolicy.SOURCE):注解仅保留在源代码中,在class文件中不保留。
@Retention(RetentionPolicy.CLASS):默认的注解保留策略,注解会在保留到编译期间,在Class文件中保留,但在运行时无法获取,即不会加载到JVM中。
@Retention(RetentionPolicy.RUNTIME):注解会在class文件保留,会被加载到JVM中,在运行时可以通过反射获取。
(二)@Target(目标):定义注解的作用目标
@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) //包
(三)@Document(文档):说明该注解将被包含在javadoc(javadoc是Sun公司提供的一个技术,它从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档。)中。
(四)@Inherited(继承):该元注解并不是说注解本身可以被继承,而是当一个类被@Inherited所标注的话,那么其子类会自动继承其父类的注解。
//使用了@Inherited元注解标注的注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(ElementType.TYPE)
@interface Father{
String value() default "father";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Mother{
String value() default "mother";
}
//其子类会继承其@Father注解
@Father
class Parent{
}
@Mother
class Child extends Parent{
}
/*
* 下面的内容可以先不看,等看完后面,再理解。
* 只需要知道结果就行了、
*/
public class ExtendsAnnotation {
public static void main(String[] args) {
try {
Class parent = Class.forName("cn.QEcode.注解.Parent");
if(parent.isAnnotationPresent(Father.class)){
Father father = (Father)parent.getAnnotation(Father.class);
System.out.println("parent "+father.value());
}
Class child = Class.forName("cn.QEcode.注解.Child");
//是否继承了父类的Father注解
if(child.isAnnotationPresent(Father.class)){
Father father = (Father)child.getAnnotation(Father.class);
System.out.println("child "+father.value());
}
//是否拥有自己的Mother注解
if(child.isAnnotationPresent(Mother.class)){
Mother mother = (Mother)child.getAnnotation(Mother.class);
System.out.println("child "+mother.value());
}
} catch (ClassNotFoundException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
//输出结果为
parent father
child father
child mother
(五)@Repeatable(可重复):表示注解的值可以同时取多个@Repeatable 是 Java 1.8 才加进来的。
/*
* 容器注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Animal{
Walk[] value();
}
@Repeatable(Animal.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Walk{
String walk();
}
/*
* 可以有多个@Walk注解
*/
@Walk(walk="跑")
@Walk(walk="爬")
class Cat{
}
/*
* 下面的内容可以先不看,等看完后面,再理解。
* 只需要知道结果就行了、
*/
public class RepeatableAnnotation {
public static void main(String[] args) {
try {
Class cat = Class.forName("cn.QEcode.注解.Cat");
Annotation[] ann = cat.getAnnotations();
for (Annotation an : ann) {
if (an instanceof Animal) {
System.out.println("Animal:");
an = (Animal) an;
Walk[] walks = ((Animal) an).value();
for (Walk walk : walks) {
System.out.println(walk.walk());
}
} else if (an instanceof Walk) {
System.out.println("Walk:");
Walk walk = (Walk) an;
System.out.println(walk.walk());
}
}
} catch (ClassNotFoundException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
//输出结果为:
Animal:
跑
爬
@Repeatable注解标注了Walk注解,并且表明了Animal是Walk的容器注解。
容器注解是指存放其他注解的注解。它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组。
同时从上面的代码可以看出,在使用反射获取Cat(被@Walk标注的注解)的注解时,需要获取的不是@Walk注解,而是容器注解@Animal,并且注解的值在@Animal中的value数组。
在使用注解来标注一个类后,我们需要在运行时获取到注解的内容。而想要获取到注解的内容,就需要用到反射。
注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解。
public boolean isAnnotationPresent(Class extends Annotation> annotationClass) {}
但是注意isAnnotation()是判断Class对象是否为一个注解
public boolean isAnnotation(){}
然后通过 getAnnotation() 方法来获取 Annotation 对象。
public A getAnnotation(Class annotationClass) {}
或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}
前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
//标注一个类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ClassDesc{
String name() default "";
int age() default 18;
boolean sex() default true;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MethodDesc{
String value() default "";
}
@ClassDesc(name="QEcode",age=13)
class UserAnnotation{
@MethodDesc("行为")
public void test(){
System.out.println("Test");
}
public void test2(){
System.out.println("Test2");
}
@MethodDesc("动作")
public void test3(){
System.out.println("Test3");
}
}
public class MyAnnotation {
public static void main(String[] args){
//使用类加载器加载类
try {
Class clazz = Class.forName("cn.QEcode.注解.UserAnnotation");
//判断这个类是否应用了ClassDesc注解
if(clazz.isAnnotationPresent(ClassDesc.class)){
//获取注解
ClassDesc classDesc = (ClassDesc) clazz.getAnnotation(ClassDesc.class);
System.out.println(classDesc.name()+" "+classDesc.age());
//提取方法
Method[] ms = clazz.getMethods();
for(Method method:ms){
if(method.isAnnotationPresent(MethodDesc.class)){
MethodDesc methDesc = (MethodDesc) method.getAnnotation(MethodDesc.class);
System.out.println(methDesc.value());
method.invoke(new UserAnnotation(), null);
}
}
}
} catch (ClassNotFoundException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}