最近在学习依赖注入框架Dagger2,里面用到了许多注解,之前很少用到注解,所以觉得上手难度有点大,所以决定先了解下java中的注解机制再去学习,这里总结下java中注解的使用,就当留个笔记了。注解(Annotation),是jdk1.5及其之后引入的一套机制。那这玩意儿到底有啥用呢?我们随意建一个类,代码如下:
public class Student {
private String name;
private int 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
一个再简单不过的实体类Student。里面有一个toString方法,将姓名和年龄打印出来。在toString方法的上面有一个 @Override标识。这个东西有啥用啊,我们平时继承一个类并重写其方法时,或者实现接口中的方法,都可以在方法上方看到这个标识。曾经看过一个关于注解的描述挺形象的。注解,相当于一个标签,标签里面记录着一些信息。举个例子?比如一件衣服的标签,里面有衣服的售价、成分、厂家等信息。是用来专门描述这件衣服的信息的。java中的注解大抵也是这个意思,是用来描述方法、类、参数的信息。就像上面的toString方法上的 @Override。告诉我们这个方法是重写父类的方法或实现接口的方法。java中还有许多内置的注解,如 @SuppressWarnings,它用于抑制编译器产生警告信息,它告诉编译器,这个方法或类由我罩着的,你不能吹毛求疵,即便它产生了警告,你也得给我忽略。注解都是标识着某种信息。java中自带的注解咱们就不多说了,关键是,我们能自己自定义注解,然后在别的地方用嘛?当然可以。
- 自定义注解
自定义注解咋玩呢?很简单。
public @interface TestAnnotation {
}
这样就成功声明了一个注解。和接口的申明方法几乎一样,唯一不同的是interface关键字前面多了个 @ 符号而已。定义好了,咋用呢?新建一个
测试类TestClass,
@TestAnnotation
public class TestClass {
}
在类的上方加上 @ 和我们刚定义的注解名字TestAnnotation,然而这个注解是个空注解,意义不大。那么这个注解能不能被我们加在方法上呢?
@TestAnnotation
public class TestClass {
@TestAnnotation
public void test() {
}
}
好像也没啥问题,代码也没有报错,但实际是不行的,为啥呢?这里先引入一个新概念:元注解。啥叫元注解啊,注解就算了,咋还有元注解,别慌大兄弟,问题不大。前面说过注解相当于标签,我们说一件衣服的标签记录了衣服的一些信息,但这个标签我不能乱贴吧,我可以贴在衣服上,但我能把这个标签贴在平底锅上么?当然不能,也不符合常理。可是这个标签可以贴在什么地方,我要怎么描述呢?很简单,在注解的上方通过注解的方式告诉编译器这个注解在什么地方生效。先看代码:
@Target(ElementType.TYPE)
public @interface TestAnnotation {
}
还是之前的注解申明,只不过在注解的最上方有一行 @Target(ElementType.TYPE),啥意思呢,它的作用就是告诉编译器这个注解只在类中生效,在方法中不起作用,我们可以在方法上加这个注解,但是它不生效。@Targe 注解可以省略,默认ElementType.TYPE,即在类中生效。那注解还能用在哪些地方呢?
@Target(ElementType.TYPE)//在类、接口(包括注解类型) 或enum声明中有效
@Target(ElementType.METHOD)//在方法中有效
@Target(ElementType.FIELD)//在成员变量上有效
@Target(ElementType.CONSTRUCTOR)//在构造方法中有效
@Target(ElementType.PACKAGE)//在包上有效
@Target(ElementType.PARAMETER)//在参数上有效
@Target(ElementType.LOCAL_VARIABLE)//在局部变量中有效
这里就不一一去写Demo了,感兴趣的可以自己挨个试一试。当然,元注解不止一个@Target,还有一个比较常用的 @Retention,那这个注解又是描述什么的呢?它描述的是注解生存的时间。即在哪个阶段有效,主要有以下几个值:
@Retention(RetentionPolicy.SOURCE)//在源码阶段存在,编译时丢弃。
@Retention(RetentionPolicy.CLASS)//在class文件中存在,运行时丢弃
@Retention(RetentionPolicy.RUNTIME)//运行时存在,所以可以通过反射机制获取注解的信息
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface TestAnnotation {
}
具体怎么使用,就看你想让你的注解在什么时候生效了。还有一个常用的元注解 @Inherited,如果一个使用了@Inherited修饰的注解类型被用于一个class,则这个类的子类也有该注解类型。就像继承一样。注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
解释一下,1.在接口中申明注解,实现类中不会继承该注解;2.方法的重载也不会继承该注解,可以继承的只有子类和父类。
- 注解的成员
上面我们定义的注解都是空的,只加了几个注解的描述,那注解有没有成员呢?当然有:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface TestAnnotation {
String name() ;
int age() default 10;
}
这里假设这个注解描述一个人的信息,有姓名和年龄。注解加了成员,使用的时候也需要初始化成员的值。
@TestAnnotation(name = "",age = 20)
public class TestClass {
public void test() {
}
}
可以看到,使用的时候需要为定义的元素赋值,否则编译器会报错。也可以为元素赋值一个默认值,用default关键字,如果注解元素设置了默认值,使用的时候可以不用为该元素赋值。像下面这样:
@TestAnnotation(name = "张三")
public class TestClass {
public void test() {
}
}
上面age元素设置了默认值10,所以使用的时候可以不赋值,也可以赋值,赋值的话以新值为准。如果注解中只有一个元素且名称为value,使用的时候可以不用key=value的方式进行赋值,直接写值就好了。注意:注解元素只支持以下类型:int、float、boolean、byte、double、char、long、short、String、Class、enum、Annotation。任何第三方的类型都不被承认。同样,任何非基本类型的元素,默认值都不可以为null,传入的值也不能为null。注解中默认所有的元素都存在并且都具有相应的值。例如String类型,默认值不可以为null,但可以用空字符串替代。
- 注解元素值的获取
注解也有了,注解元素也有了,并且也可以为注解元素赋值了,那么外部怎样拿到元素的值呢?注解中可没有定义get和set方法。答案是反射。本篇只涉及注解的内容,如果各位看官不了解反射,还请自行百度下哈~
- 获取类上上申明的注解
新建一个Person类,用TestAnnotation注解修饰:
@TestAnnotation(name = "张三",age = 20)
public class Person {
}
获取Person注解中元素的值:
public class TestClass {
public void test1() {
//获取Peron类的字节码对象
Class personClazz = Person.class;
//获取Person上申明的名称为TestAnnotation的注解。如果Person没有申明该注解,返回null。
TestAnnotation testAnnotation = (TestAnnotation) personClazz.getAnnotation(TestAnnotation.class);
//获取注解元素的值。
Log.e("姓名---------", testAnnotation.name());
Log.e("年龄---------", testAnnotation.age() + "");
}
}
看下结果
- 获取方法上申明的注解
需修改一下TestAnnotation的@Targer元注解为METHOD。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestAnnotation {
String name() ;
int age() default 10;
}
Person类新增方法,并加上注解。
public class Person {
@TestAnnotation(name = "张三", age = 20)
public void getNameAndAge() {
}
}
获取方法中申明的注解的元素的值
public class Test {
public void test1() {
Class personClazz = Person.class;
Method method;
try {
//获取Person类中名称为getNameAndAge的方法。
method = personClazz.getDeclaredMethod("getNameAndAge");
//获取getNameAndAge方法上申明的注解。如果不存在返回null。
TestAnnotation testAnnotation = method.getAnnotation(TestAnnotation.class);
//获取注解元素的值。
Log.e("姓名---------", testAnnotation.name());
Log.e("年龄---------", testAnnotation.age() + "");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
结果还是一样的哈:
- 获取字段上的注解的元素的值,修改TestAnnotation的@Targer注解为Field。Person新加一个字符串变量。代码如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestAnnotation {
String name() ;
int age() default 10;
}
public class Person {
@TestAnnotation(name = "张三", age = 10)
private String TEST = "test";
}
获取该字段申明的注解的元素的值:
public class TestClass {
public void test1() {
//获取类的字节码对象
Class personClazz = Person.class;
Field field;
try {
//获取类中申明的名称为TEST的字段
field = personClazz.getDeclaredField("TEST");
//访问私有成员
field.setAccessible(true);
//获取字段上申明的名称为TestAnnotation的注解,找不到返回null。
TestAnnotation testAnnotation = field.getAnnotation(TestAnnotation.class);
//获取注解元素的值。
Log.e("姓名---------", testAnnotation.name());
Log.e("年龄---------", testAnnotation.age() + "");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
结果依旧相同,还有获取构造方法上及参数上的元素值,这里就不一一演示了,感兴趣的朋友可以自己试一试。不过这东西平时开发时用的不多,但是许多框架中都会用到注解,比如Android中大名鼎鼎的网络框架Retrofit,就用注解的形式来获取请求的方法和配置请求地址。举个例子:
public interface API {
/**
* 获取公众号列表
* @return Observable<PublicAccountBean>
*/
@POST("wxarticle/chapters/json")
@FormUrlEncoded
Observable<PublicAccountBean> getPublicAccountList(@FieldMap Map<String,String> map);
}
通过代码中配置BaseUrl和注解中设置的值组成真正的请求地址。好啦,这只是其中一个小例子。还有许多框架都用到注解,了解注解、反射能够帮助我们更好的了解框架的实现原理。能看懂别人的代码,才能更好的使用不是?
完。。。。。