Annotation注解
什么是注解?
注解:Annotation是从JDK5.0开始引入的新技术。可以将它理解成“元数据”,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码:
@Override
public String toString() {
return "This is String Representation of current object.";
}
上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。
总结什么是注解:
1):不是程序本身,可以对程序作出解释。(这一点,跟注释没什么区别)
2): 可以被其他程序(比如:编译器等)读取。(注解信息处理流程,是注解和注释的重大区别 。如果没有注解信息处理流程,则注解毫无意义)
预置注解
- @Override
:空注解,用于标记那些覆盖父类方法的方法,如果父类没有这个方法,或者复写的方法访问权限比父类的权限小,编译器就会报错; - @Deprecated : 空注解,用于标记那些不应该被使用的代码,如果使用了过时的代码,编译器会发出警告;
- @SafeVarargs : 空注解,(varargs可变参数)用于标记构造函数或者方法,通知编译器,这里的可变参数相关的操作保证安全;
- @FunctionInterface : Java SE 8 出现的,用于通知编译器,这个类型是 function 接口;
- @SuppressWarning:抑制错误,可以用于标记整个类、某个方法、某个属性或者某个参数,用于告诉编译器这个代码是安全的,不必警告(强烈建议最小范围使用这个注解,一旦你在一个比较大的范围抑制错误,可能会把真正的问题掩盖了)。
元注解
元注解的作用就是负责注解其他注解。 Java定义了4个标准的 meta-annotation类型,它们被用来提供对其它 annotation 类型作说明。
这些类型和它们所支持的类在java.lang.annotation包中可以找到 。
– @Target
– @Retention
– @Documented
– @Inherited
- @Target的作用:用于描述注解的使用范围,可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。
ElementType.ANNOTATION_TYPE :可以给一个注解进行注解;
ElementType.CONSTRUCTOR :可以给构造方法进行注解;
ElementType.FIELD :可以给属性进行注解;
ElementType.LOCAL_VARIABLE :可以给局部变量进行注解;
ElementType.METHOD :可以给方法进行注解;
ElementType.PACKAGE :可以给一个包进行注解;
ElementType.PARAMETER :可以给一个方法内的参数进行注解;
ElementType.TYPE :可以给一个类型进行注解,比如类、接口、枚举。
- @Inherited的作用:当前注解是否可以继承。但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
-
@Documented的作用:它的作用是能够将注解中的元素包含到 Javadoc 中去。
-
@Retention的作用: 指出注解如何存储,支持以下三种参数:
RetentionPolicy.SOURCE : 注解只保留在源码中,编译时会忽略 ;
RetentionPolicy.CLASS : 更高一级,编译时被编译器保留,但是运行时会被 JVM 忽略 ; RetentionPolicy.RUNTIME :最高级,运行时会被保留,可以被运行时访问。
注解的用处:
注解可以附加在package, class, method, field等上面,相当于给它们添加了额外的辅助信 息,我们可以通过反射机制编程实现对这些元数据的访问。
- 提供信息给编译器:编译器可以利用注解来探测错误和警告信息;
- 编译阶段时的处理:软件工具可以利用注解信息来生成代码 html 文档或者其他相应的处理;
- 运行时的处理:某些注解可以在程序运行的时候接受代码的提取。
注解的好处:
- 提升开发效率;
- 编译期间就可以验证正确性,差错很容易;
- 保存在 class 文件中,降低维护成本;
注解的坏处:
- 若要对配置项进行修改,需要修改 Java 文件,还需要重新打包编译;
- 配置项编码在 Java 文件中,可扩展性差。
自定义注解
第一步:定义注解
创建注解时,需要声明的类型为 @interface,看起来和接口有一些相似哈,其中的 @ 标明这是一个注解类型( “@ ~ annotation type”):
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
//属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
int value();
}
注解除了名字和接口有些相似,内容也很相似,都是声明一个方法,规定返回值,不同的是这里的方法其实是个属性,返回值规定了属性的类型(至于为什么要声明成方法而不是属性,可能是为了后续直接使用这个方法获取值比较直观吧)。
注意:如果你的注解中创建了多个属性,但是使用时只需要使用某几个,这时编译器会提示你有没有指明的属性。可以使用 default为注解的某个属性指定默认值,这样即使不指定某个属性,编译器也不会报错。这通常可以节约很多时间,比如这样:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Author {
String name() default "ABC";
String date();
}
当我们使用 @Author 时没有指定 name = XXX,则会默认为 “ABC”。
定义注解类型元素时需要注意如下几点:
- 访问修饰符必须为public,不写默认为public;
- 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
- 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
- ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
- default代表默认值,值必须和第2点定义的类型一致;
- 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在使用时可以感受到:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。
第二步:配置注解
到目前为止我们只是完成了第一步,接下来我们就来学习第二步,配置注解,如何在另一个类当中配置它。首先,定义一个注解、和一个供注解修饰的简单Java类。
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface CherryAnnotation {
String name();
int age() default 18;
int[] score();
}
public class Student{
public void study(int times){
for(int i = 0; i < times; i++){
System.out.println("Good Good Study, Day Day Up!");
}
}
}
分析:
- CherryAnnotation的@Target定义为ElementType.METHOD,那么它书写的位置应该在方法定义的上方,即:public void study(int times)之上;
- 由于我们在CherryAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;
所以最终形式如下:
public class Student {
@CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77})
public void study(int times){
for(int i = 0; i < times; i++){
System.out.println("Good Good Study, Day Day Up!");
}
}
}
需要注意以下几个特殊语法:
- 如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface FirstAnnotation {
}
//等效于@FirstAnnotation()
@FirstAnnotation
public class JavaBean{
//省略实现部分
}
- 如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)!
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface SecondAnnotation {
String value();
}
//等效于@ SecondAnnotation(value = "this is second annotation")
@SecondAnnotation("this is annotation")
public class JavaBean{
//省略实现部分
}
- 如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
//省略实现部分
}
- 如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。
第三步:解析注解
注解保持力的三个阶段:Java源文件阶段;编译到class文件阶段;运行期阶段。
只有当注解的保持力处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。
因此,明确我们的目标:在运行期探究和使用编译期的内容(编译期配置的注解),要用到Java中的灵魂技术——反射机制!
我们先自定义一个 ContentView 注解,表示当前布局对应的 layout 文件:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
//属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
int value() ;
}
然后用它修饰一个 Activity:
@ContentView(R.layout.activity_annotation)
public class AnnotationTestActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //调用父类的 onCreate
setContentView(R.layout.activity_annotation);
}
}
在 BaseActivity 中反射获取当前类使用的注解,拿到注解的值,就可以直接设置布局了:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
annotationProcess();
}
//读取注解,进行处理
private void annotationProcess() {
Class c = this.getClass();
//遍历所有子类
for (; c != Context.class; c = c.getSuperclass()) {
//找到使用 ContentView 注解的类
ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
if (annotation != null) {
try { //有可能出错的地方都要 try-catch
//获取 注解中的属性值,为 Activity 设置布局
this.setContentView(annotation.value());
} catch (RuntimeException e) {
e.printStackTrace();
}
return;
}
}
}
这样就简单实现了运行时根据注解动态设置布局的功能。
瑞思拜!!!