在一些强大的第三方框架中我们常常可以见到注解的身影。xUtils、Retrofit等。
那么注解到底有什么魅力和好处让我们在设计种种框架的时候用到它呢?
对于注解的理解:
(仅仅为个人理解)
1,我们通过注解给某个常量、方法或者类一个标记。那么这些常量就有了某种特征或者某种标记。
2,这些标记不参与我们的逻辑处理。这也是为什么它会写在方法或者类的外面的原因。
3,但是我们可以通过获取方法上的标记即注解来间接的影响程序的逻辑。
4,注解是被动的,它影响程序取决于程序员是否去使用它。
由此看来,注解的作用就是给某个方法或者类一个标记,这些标记是被动的,不影响程序的逻辑,但是程序员可以通过注解主动的去影响程序。
注解(Annotation)在java5之后是和类(class)、接口(interface)、枚举(enum)同级的一个特征。
在as中就可以直观的看出,它们四个是可以直接被创建的。
那么,注解在java中是怎么表现的呢?
@Retention @Target @Documented @Inherited四种。
1,@Documented
如果你的常量、方法、类(每次都打这么多好累,下面简化为常方类)加上了这个注解,那么在你项目下的javadoc文档中就会包含这个方法。
这个见过的也很多了,很多三方的工具库的接口文档。类似于这种:
就是在你需要生成javadoc的时候加上该注解生成的接口文档。
2,@Inherited
执行到inherited这一行的时候,自动执行父类的相关代码,执行完后再回来执行子类的。
看代码更加直观:
DBTable被标记了@Inherited
@Inherited
public @interface DBTable {
public String name() default "";
}
public @interface DBTable2 {
public String name() default "";
}
@DBTable
class Super{
private int superPrivateM(){
return 0;
}
public int superPubliceM(){
return 0;
}
}
@DBTable2
class Sub extends Super{
private int subPrivateM(){
return 0;
}
public int subPubliceM(){
return 0;
}
}
super类被标记带有@Inherited的注解。所以,当sub继承super的时候,执行sub中的subPubliceM()方法的时候会先执行super中的subPubliceM()方法。
3,@Target
Target可以定义注解被使用的位置。
在使用时要指定一个java.lang.annotation.ElementType的枚举值类型为他的“属性”。
ElementType枚举的类型如下:
ANNOTATION_TYPE: 适用于annotation
CONSTRUCTOR : 适用于构造方法
FIELD : 适用于field
LOCAL_VARIABLE : 适用于局部变量
METHOD : 适用于方法
PACKAGE : 适用于package
PARAMETER: 适用于method上的parameter
TYPE : 适用于class,interface,enum
@Target(ElementType.METHOD)
public @interface TargetTest {
//这样,这个枚举就只能使用于方法而不能使用与变量或者类了
String hello() default "hello";
}
4,@Retention
Retention用来说明该注解类的生命周期。
使用Retention必须要提供一个为java.lang.annotation.RetentionPolicy类型的的枚举。
RetentionPolicy枚举有以下3个类型:
SOURCE : 编译程序时处理完Annotation信息后就完成任务
CLASS: 编译程序将Annotation存储于class文件中,不可以由虚拟机读入
RUNTIME: 编译程序将Annotation存储于class文件中,可以由虚拟机读入
用这三种Retention的Prolicy可以决定注解是从源文件,class文件或者以在运行时反射被读取
注意:
如果在注解上没有添加Retention注解,系统是会默认添加,并且为CLASS类型。
也就是说,如果你想在虚拟机中读取这个枚举多附带的内容,必须设置为RUNTIME。
@Retention(RetentionPolicy.RUNTIME)
//定义一个注解,使用Retention标注为RUNTIME该注解被标示为runtime类型,表示该注解最后可以保存在class文件中,并为java虚拟机在运行时读取到
public @interface RetentionTest {
String hello() default "hello";
String world();
@Retention(RetentionPolicy.CLASS)
//定义一个注解,Retention标注为CLASS,在代码中是获取不到注解中的内容的
@interface RetentionTest1 {
String hello() default "hello"; //设置默认值为hello
}
}
好了,系统自带的,修饰注解的注解(元注解)就是这四个。
接下来看系统自带的注解,这里就不再是元注解了。
1,@SuppressWarnings
系统对代码警告的处理方式。
@SuppressWarnings(“unchecked”)
告诉编译器忽略 unchecked 警告信息
@SuppressWarnings(“serial”)
如果编译器出现这样的警告信息:The serializable class WmailCalendar does not declare a static final serialVersionUID field of type long使用这个注释将警告信息去掉。
@SuppressWarnings(“deprecation”)
如果使用了@Deprecated注释的方法,编译器将出现警告信息。使用这个注释将警告信息去掉。
@SuppressWarnings(“unchecked”, “deprecation”)
告诉编译器同时忽略unchecked和deprecation的警告信息。
@SuppressWarnings(value={“unchecked”, “deprecation”})
等同于@SuppressWarnings(“unchecked”, “deprecation”)
@SuppressWarnings("unchecked")
private void aaa() {
}
2,@Override
这个也不用解释了吧。就算没用过注解也见多了。
算了,还是解释一下。子类要重写(override)父类的对应方法。
@Override
private void bbb() {
//super的方法决定你需不需要先执行父类的方法
super.bbb();
}
3,@Deprecated
/** * Deprecated 注解表示方法是不建议被使用的,过期的 */
@Deprecated
private void ccc() {
}
好了,系统枚举我已经介绍完了。接下来就说说如何自定义枚举和如何使用枚举。
使用@interface来声明一个注解(实际上是自动继承了java.lang.annotation.Annotation接口)
元注解可以重叠使用
/** * Created by Luhao on 2016/5/20. * 自定义注解 * 使用@interface来声明一个注解(实际上是自动继承了java.lang.annotation.Annotation接口) */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
String name() default "test"; //为注解设置String类型的属性Value1,并使用defalut关键字设置默认值
EnumTest type();//设置枚举类型的value2
String[] skills();//设置数组类型的value3
}
public enum EnumTest {
IT,
EAJUEJI,
CHUSHI,
ZUOJIA,
JINGCHA,
;
}
加入其他元注解
/** * Retention: * 使用Retention必须要提供一个为java.lang.annotation.RetentionPolicy类型的的枚举 * RetentionPolicy枚举有以下3个类型: * SOURCE : 编译程序时处理完Annotation信息后就完成任务 * CLASS: 编译程序将Annotation存储于class文件中,不可以由虚拟机读入 * RUNTIME: 编译程序将Annotation存储于class文件中,可以由虚拟机读入 * 用这三种Retention的Prolicy可以决定注解是从源文件,class文件或者以在运行时反射被读取 */
@Retention(RetentionPolicy.RUNTIME)
//定义一个注解,使用Retention标注为RUNTIME该注解被标示为runtime类型,表示该注解最后可以保存在class文件中,并为java虚拟机在运行时读取到
public @interface RetentionTest {
String hello() default "hello";
String world();
@Retention(RetentionPolicy.CLASS)
//定义一个注解,Retention标注为CLASS,在代码中是获取不到注解中的内容的
@interface RetentionTest1 {
String hello() default "hello"; //设置默认值为hello
}
}
/** * Target: * 使用java.lang.annotation.Target可以定义注解被使用的位置 * 同样,在使用时要指定一个java.lang.annotation.ElementType的枚举值类型为他的“属性” * ElementType枚举的类型如下: * ANNOTATION_TYPE: 适用于annotation * CONSTRUCTOR : 适用于构造方法 * FIELD : 适用于field * LOCAL_VARIABLE : 适用于局部变量 * METHOD : 适用于方法 * PACKAGE : 适用于package * PARAMETER: 适用于method上的parameter * TYPE : 适用于class,interface,enum */
@Target(ElementType.METHOD)
public @interface TargetTest {
//这样,这个枚举就只能使用于方法而不能使用与变量或者类了
String hello() default "hello";
}
/** * 各种枚举的测试类 */
public class Test {
/** * java注解系统自带有主要以下几个注解 * 只需要使用@interface来定义一个注解 * 修饰注解的“注解” * 注解的注解。包括 @Retention @Target @Document @Inherited四种。 * * @Document 会将该注解写入javadoc中 * @Inherited 执行到inherited这一行的时候,自动执行父类的相关代码,执行完后再回来执行子类的 */
private void test() {
aaa();
bbb();
ccc();
}
/** * SuppressWarnings 注解表示抑制警告,程序的警告将被忽略 */
@SuppressWarnings("unchecked")
private void aaa() {
}
/** * Override 注解表示子类要重写(override)父类的对应方法 */
//@Override
private void bbb() {
//super的方法决定你需不需要先执行父类的方法
//super.bbb();
}
/** * Deprecated 注解表示方法是不建议被使用的,过期的 */
@Deprecated
private void ccc() {
}
private class TestThread extends Thread {
@Override
public void run() {
}
}
private class aaa implements Runnable{
@Override
public void run() {
}
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
}
private ThreadPoolExecutor threadPoolExecutor;
/** * 自定义注解 * 需要完成注解中所有的属性,如果属性设置了默认值可以忽略 */
@AnnotationTest(name = "阿里山的姑娘", type = EnumTest.IT, skills = {"java", "c", "c++"})
private void ddd() {
}
/** * 自定义注解还可以使用在方法,变量和类上 */
@AnnotationTest(name = "阿里山的姑娘", type = EnumTest.IT, skills = {"java", "c", "c++"})
String alishan;
@TargetTest(hello = "阿里山的菇凉") //标注在方法上不会报错
private void eee() {
}
//@TargetTest(hello = "阿里山的菇凉")//这里则会报错,因为他标注在类上面了
String alishans;
/** * 在接口中有以下重要的方法: * getAnnotations(Class annotationType) 获取一个指定的annotation类型 * getAnnotations() 获取所有的Annotation * getDeclaredAnnotations() 获取声明过的所有Annotation * isAnnotationPresent(Class<? extends Annotation> annotationClass) 这个annotation是否出现 */
@SuppressWarnings("unchecked") //java自带的注解Retention的policy为SOURCE,该注解的意思是这个方法中的警告将被忽略
@Deprecated//java自带的注解Retention的policy为RUNTIME
@RetentionTest(hello = "Dean", world = "25") //自定义的注解Retention的policy为RUNTIME
@RetentionTest.RetentionTest1(hello = "aaaaaaa")//自定义的注解Retention的policy为CLASS
public void TestMethod() {
System.out.println("this is a method");
}
}
public class MainTest {
public void mainTest() {
Test testa = new Test();
//通过反射得到TestMethod方法
Class<Test> cla = Test.class;
try {
//找到test类中的TestMethod方法
Method method = cla.getMethod("TestMethod");
//AnnotatedElement接口中的方法isAnnotationPresent(),判断传入的注解类型是否存在
if (method.isAnnotationPresent(RetentionTest.class)) {
method.invoke(testa, new Object[]{});
//AnnotatedElement接口中的方法getAnnotation(),获取传入注解类型的注解
RetentionTest retentionTest = method.getAnnotation(RetentionTest.class);
//拿到注解中的属性
String hello = retentionTest.hello();
String world = retentionTest.world();
Log.i("test", "name:" + hello + " age:" + world);
}
//因为RetentionTest1的枚举定义为CLASS,所以它是不会在虚拟机中运行的
if (method.isAnnotationPresent(RetentionTest.RetentionTest1.class)) {
method.invoke(testa);
//AnnotatedElement接口中的方法getAnnotation(),获取传入注解类型的注解
RetentionTest.RetentionTest1 retentionTest = method.getAnnotation(RetentionTest.RetentionTest1.class);
//拿到注解中的属性
String hello = retentionTest.hello();
Log.i("test", "nameaaaaa:" + hello );
}
//AnnotatedElement接口中的方法getAnnotations(),获取所有注解
Annotation[] annotations = method.getAnnotations();
//循环注解数组打印出注解类型的名字
for (Annotation annotation : annotations) {
Log.i("test", annotation.annotationType().getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们可以得到注解中的数据,然后通过这些标记的值来主动的选择是否对方法逻辑产生作用。
好了,这就是注解的使用。接下来还会讲到java反射机制。
注解和反射式两个相互配合才更好用的东西。