现在许多主流框架都会用到 注解(Annotation) 的技术。
为了更好的理解及使用这些库,所以对注解做了整理。
注解
什么是注解
注解(Annotation) 是 Java 5 的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用 Java 反射机制进行处理。
JDK 内置注解
为了便于理解,我们先看看 Java内置的注解。
JavaSE 中内置三个标准注解,定义在 java.lang 中:
- @Override:用于修饰此方法覆盖了父类的方法;
- @Deprecated:用于修饰已经过时的方法;
- @SuppressWarnnings:用于通知java编译器禁止特定的编译警告。
常见示例:
@Override
public String toString() {
return "Test Override annotation!";
}
上面示例重写了 toString()
方法,并使用了@Override
注解。但是,即使我不使用@Override
注解标记代码,程序也能够正常执行。@Override
注解的作用就是告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错。
可以看到,这个注解非常实用,如果我们一不小心将 toString()
写成了tostring()
,那么标记这个注解时,编译器就会提醒我们写错了。
其实,注解的作用远远不仅不如此,AOP(Aspect Oriented Programming,面向切面编程) 中就大量使用注解,以此来简化代码。
JDK 中内置的注解数量和功能有限,想要更加强大的功能,必须学会自定义注解。
元注解
在学习自定义注解前,我们必须先学习 元注解。
什么是元注解
元注解 就是描述注解的注解。这话有点绕,简单来说,元注解 就是用来定义注解的。
JDK 中内置的注解也是通过元注解来实现的。
例如 Override 的注解是这样定义的:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
其中,Target
和 Retention
就是 元注解 ,它们共同描述了Override 的一些特性。
四种元注解
JavaSE5.0 版本在 java.lang.annotation 提供了四种元注解:
- @Documented –注解是否将包含在 JavaDoc 中
- @Retention –什么时候使用该注解
- @Target –注解用于什么地方
- @Inherited – 是否允许子类继承该注解
@Documented
一个简单的Annotations标记注解,表示是否将注解信息添加在 java 文档中。
@Retention
定义该注解的生命周期。其中有三个值可选。
RetentionPolicy.SOURCE
注解仅存在于源码中,在class字节码文件中丢弃。@Override, @SuppressWarnings 都属于这类注解。
RetentionPolicy.CLASS
默认的保留策略,注解会在 class 字节码文件中存在,在类加载的时候(即运行期)丢弃。
RetentionPolicy.RUNTIME
运行期也保留该注解,始终不会丢弃,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
@Target
定义该注解用于什么地方。如果不明确指出,该注解可以放在任何地方。
以下是一些可用的参数:
- ElementType.TYPE -用于描述类、接口或 enum 声明
- ElementType.FIELD -用于描述成员变量
- ElementType.METHOD -用于描述方法
- ElementType.PARAMETER -用于描述参数
- ElementType.CONSTRUCTOR -用于描述构造器
- ElementType.LOCAL_VARIABLE -用于描述局部变量
- ElementType.ANNOTATION_TYPE -用于描述注解类型
- ElementType.PACKAGE -用于记录java文件的package信息
可能有两个参数 ANNOTATION_TYPE
和PACKAGE
比较难以理解。
ANNOTATION_TYPE
表示该注解是用于描述注解的。这话很绕,举个例子,所有的 元注解 都被标注了@Target(ElementType.ANNOTATION_TYPE)
这个注解。例如,元注解@Target 本身:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
我们说 元注解 就是描述注解的注解,它和普通注解的区别就在于 ANNOTATION_TYPE
这个参数。所有我们也可以定义元注解。
PACKAGE
表示这个注解是标注包信息,而且注解必须应用在是 package-info.java
这个文件,否则会报错的。
例如:
@Target({ElementType.PACKAGE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Test{
String name();
String values();
}
需要说明的是:属性的注解是兼容的,如果你想给7个属性都添加注解,仅仅排除一个属性,那么你需要在定义target包含所有的属性。
上面的示例中, @Test 注解可以标注包,也可以标注方法。标注包时,必须新建 package-info.java
的文件
@Test (name="package", values="我在包里")
package com.deemons.annotation;
然后在使用
Package p = Package.getPackage("com.deemons.annotation");
if(p!=null && p.isAnnotationPresent(Test.class)){
Test test = p.getAnnotation(Test.class);
if(test !=null){
System.out.println(test.values()+"======================="+test.name());
}
}
@Inherited
Inherited 元注解是一个标记注解,表示是否允许子类继承该注解。
比如,一个使用了@Inherited修饰的注解被用于一个class,则这个注解将被用于该class的子类。
自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
比如,最简单是注解:
public @interface MyAnnotation {
}
它没有任何元注解的描述,这个注解可以作用在任何地方。
根据前面讲的 元注解 ,你可以用 @Retention 控制它的生命周期,不声明就默认保留到字节码时期;
你可以用 @Target 控制它的作用目标,不声明就默认可以作用在任何地方;
你可以使用 @Documented 将注解添加到 文档中;
你还可以使用 @Inherited 让子类继承该注解。
定义单变量
现在这些还不够,我们看到有些注解,使用时需要添加参数。
例如,最常见的@SuppressWarnings("unchecked")
,这是因为定义该注解时添加了变量:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
可以看到,SuppressWarnings
里面有String[] value();
这么一行,感觉就像定义接口的方法一样样的。
虽然形式和定义接口很像,但还是有区别的。
String[]
表示注解中接收的参数类型是String 的数组。
value()
表示注解中接收的参数名称是 value。
注意:当定义单个变量,并且注解中使用的属性名为value时,对其赋值时可以不指定属性的名称而直接写上属性值接口;除了value 以外的变量名都需要使用name=value的方式赋值。
例如:
public @interface MyAnnotation {
String type();
}
以上使用时就必须是 @MyAnnotation(type="string")
这种方式,
public @interface MyAnnotation {
String value();
}
而这样就可以 @MyAnnotation("string")
使用了。
数组变量的使用
还有一点要说明,就是如果注解中定义的参数是数组类型,例如:
public @interface MyAnnotation {
String[] names();
}
那么在使用时,就是这样的
@MyAnnotation(names={"a","b","c"})
当然,如果将 参数名称 names
变为 value
,则在使用时,也可以简写成
@MyAnnotation({"a","b","c"})
如果传入的数组参数的数量只有一个,那么也可以去掉大括号。
定义多变量
需要定义多个参数也简单。
public @interface MyAnnotation {
int value();
String[] names();
}
使用时如下:
@@MyAnnotation(value=1,names={"a","b","c"})
定义默认值
注解中的变量也是可以添加默认值的。
public @interface MyAnnotation {
String value() default "abc";
}
如上,我们为其添加了默认值abc
,这样的话,我们在使用时,就可以不必传入参数,如果不传入参数,则使用默认值abc
。
代替枚举
在Android 中极力不推荐使用枚举,因为移动设备的性能有限,而枚举却是一个消耗内存的大户,因此就有了利用注解技术来代替枚举的方案。
Android 中提供了代替枚举的注解 StringDef
和 IntDef
,注解定义在 android.support.annotation
包中:
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface StringDef {
/** Defines the allowed constants for this element */
String[] value() default {};
}
在使用时,如下:
public static final String A = "a";
public static final String B = "b";
public static final String C = "c";
@StringDef({A,B,C})//定义取值范围A、B、C
public @interface Type {
}
@Type//限定变量取值必须在范围内
static String sType;
@Type//表示返回值必须是 定义范围的值
public String getType() {
return sType;
}
//表示传入的参数必须是范围内的值
public void setType(@Type String type) {
sType =type;
}
后记
至此,Java 的 注解(Annotation) 也就弄清楚了,但注解其实在程序中仅仅起到标记的作用,要想真正运用注解,一般会结合反射,拿到注解的信息做对应处理。更高级的是使用注解处理器,在编译器就做处理。
接下来就来讲讲 注解处理器(Annotation Processor ) 。