注解
1. 速查清单
2. 注解的概述
a. 注解的概念
- 注解是 JDK1.5 的新特性;
- 注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息;
- 标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上;
- 注解是给编译器或 JVM 看的,编译器或 JVM 可以根据注解来完成对应的功能;
- 注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac 编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上;
b. 注解的作用
- 注解的作用就是给程序带入参数。
- 以下几个常用操作中都使用到了注解:
- 生成帮助文档:@author 和 @version
- @author:用来标识作者姓名;
- @version:用于标识对象的版本号,适用范围:文件、类、方法;
- 使用 @author 和 @version 注解就是告诉 Javadoc 工具在生成帮助文档时把作者姓名和版本号也标记在文档中。如下图:
- 编译检查:@Override
- @Override:用来修饰方法声明;
- 用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图:
- 框架的配置(框架 = 代码 + 配置)
- 具体使用请关注框架课程的内容的学习;
- 生成帮助文档:@author 和 @version
c. 常见注解
- @author:用来标识作者名,Eclipse 开发工具默认的是系统用户名;
- @version:用于标识对象的版本号,适用范围:文件、类、方法;
- @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败;
3. 自定义注解
a. 定义格式
public @interface 注解名{
}
//如:定义一个名为Student的注解
public @interface Student {
}
b. 注解的属性
i. 属性的格式
- 格式 1:数据类型 属性名();
- 格式 2:数据类型 属性名() default 默认值;
ii. 属性定义示例
// 姓名
String name();
// 年龄
int age() default 18;
// 爱好
String[] hobby();
iii. 属性适用的数据类型
- 八种数据数据类型(int, short, long, double, byte, char, boolean, float);
- String,Class,注解类型,枚举类;
- 以上类型的数组形式;
4. 使用自定义注解
a. 定义和注解
- 定义一个注解:Book
- 包含属性:String value() 书名;
- 包含属性:double price() 价格,默认值为 100;
- 包含属性:String[] authors() 多位作者;
- 代码实现
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
String value();
double price() default 100;
String[] authros();
}
b. 使用注解
- 定义类在成员方法上使用 Book 注解
- 使用注意事项:
- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值;
- 如果属性没有默认值,那么在使用注解时一定要给属性赋值;
c. 特殊属性 value
/**
特殊属性value
* 如果注解中只有一个属性且名字叫value,则在使用该注解时可以直接给该属性赋值,而不需要给出属性名。
* 如果注解中除了value属性之外还有其他属性且只要有一个属性没有默认值,则在给属性赋值时
value属性名也不能省略了。
小结:如果注解中只有一个属性时,一般都会将该属性名命名为value
*/
@interface TestA{
String[] value();
int age() default 100;
String name();
}
@interface TestB{
String name();
}
@TestB(name = "zzz")
@TestA(name = "yyy",value = {"xxx","xxx"})
public class Test {
}
5. 注解之元注解
a. 元注解概述
- Java 官方提供的注解;
- 用来定义注解的注解;
- 任何官方提供的非元注解的定义都使用到了元注解;
b. 常用的元注解
- @Target
- 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置;
- 可使用的值定义在 ElementType 枚举类中,常用值如下:
TYPE:类,接口
FIELD:成员变量
METHOD:成员方法
PARAMETER:方法参数
CONSTRUCTOR:构造方法
LOCAL_VARIABLE:局部变量
- @Retention
- 作用:用来标识注解的生命周期(有效范围)
- 可使用的值定义在 RetentionPolicy 枚举类中,常用值如下:
SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
RUNTIME:注解作用在源码阶段、字节码文件阶段、运行阶段
6. 注解解析
a. 什么是注解解析?
- 使用 Java 技术获得注解上数据的过程则称为注解解析;
b. 与注解解析相关的接口
Annotation
:注解类,该类是所有注解的父类;AnnotatedElement
:该接口定义了与注解解析相关的方法;T getAnnotation(Class<T> annotationClass)
:根据注解类型获得对应注解对象Annotation[] getAnnotations()
:获得当前对象上使用的所有注解,返回注解数组,包含父类继承的;Annotation[] getDeclaredAnnotations()
:获得当前对象上使用的所有注解,返回注解数组,只包含本类的;boolean isAnnotationPresent(Class<Annotation> annotationClass)
:判断当前对象是否使用了指定的注解,如果使用了则返回 true,否则 false;
c. 获取注解数据的原理
- 注解作用在哪个成员上就会得该成员对应的对象来获得注解:
- 比如注解作用成员方法,则要获得该成员方法对应的 Method 对象;
- 比如注解作用在类上,则要该类的 Class 对象;
- 比如注解作用在成员变量上,则要获得该成员变量对应的 Field 对象;
- Field,Method,Constructor,Class 等类都是实现了 AnnotatedElement 接口;
d. 注解解析的代码实现
- 获取注解所在的那个类的 Class 对象;
- 获取注解所在的对象(可能 Field,Method,Constructor);
- 判断获取的对象中是否有该注解存在;
- 如果有我们要的注解,取出我们要的注解即可;
- 从注解中取出属性值即可;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Student {
int age();
String name();
String[] boyFriends();
}
public class StudentDemo {
@Student(age = 18, name = "小花", boyFriends = {"张三", "李四", "王五"})
public void show() {
}
}
import java.lang.reflect.Method;
import java.util.Arrays;
public class TestStudent {
public static void main(String[] args) throws Exception {
// a.获取注解所在的那个类的Class对象
Class clazz = StudentDemo.class;
// b.获取注解所在的对象(可能Field,Method,Constructor)
Method showMethod = clazz.getMethod("show");
// c.判断获取的对象中是否有该注解存在
if (showMethod.isAnnotationPresent(Student.class)) {
System.out.println("有该注解");
// d.如果有我们要的注解,取出我们要的注解即可
Student anno = showMethod.getAnnotation(Student.class);
// e.从注解中取出属性值即可
int age = anno.age();
String name = anno.name();
String[] friends = anno.boyFriends();
System.out.println("姓名:" + name);
System.out.println("年龄:" + age);
System.out.println("男友们:" + Arrays.toString(friends));
} else {
System.out.println("没有该注解");
}
}
}
/*
输出
有该注解
姓名:小花
年龄:18
男友们:[张三, 李四, 王五]
*/
7. 注解案例
a. 案例说明
- 模拟 Junit 测试的 @Test
b. 案例分析
- 模拟 Junit 测试的注释 @Test,首先需要编写自定义注解 @MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得;
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest 注解,编写三个方法,其中两个加上 @MyTest 注解;
- 最后编写调用类,使用 main 方法调用目标类,模拟 Junit 的运行,只要有 @MyTest 注释的方法都会运行;
c. 案例代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义的注解,用于模拟@Test注解
*/
@Retention(RetentionPolicy.RUNTIME) // 注解的存活周期到任何阶段
@Target(ElementType.METHOD) // 只能注解方法了
public @interface MyTest {
}
import java.lang.reflect.Method;
public class Demo {
// 自己模拟那个启动按钮。只启动有注解的方法
public static void main(String[] args) throws Exception {
Demo t = new Demo();
// 扫描所有的方法对象,看方法上是否有注解,有就触发它。
Class clazz = Demo.class;
// 获取全部的方法
Method[] methods = clazz.getDeclaredMethods();
// 遍历全部方法
for (Method mt : methods) {
// 判断这个方法是否有MyTest注解,有就触发它执行
if (mt.isAnnotationPresent(MyTest.class)) {
// 触发这个方法执行!
mt.invoke(t);
}
}
}
@MyTest
public void run1() {
System.out.println("===run1===");
}
public void run2() {
System.out.println("===run2===");
}
public void run3() {
System.out.println("===run3===");
}
@MyTest
public void run4() {
System.out.println("===run4===");
}
}
/*
输出
===run1===
===run4===
*/
8. 练习题
a. 问答题
1. 请问定义注解的关键字是什么?
- @interface
2. 请例举我们使用过的一些注解,以及它们都有什么作用?
- @author:用来标识作者名,Eclipse 开发工具默认的是系统用户名;
- @version:用于标识对象的版本号,适用范围:文件、类、方法;
- @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败;
3. 请问我们学过哪些元注解?它们都有什么作用?
- @Target:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置;
- @Retention:用来标识注解的生命周期;
4. 请问“注解”中定义“属性”时可以使用的数据类型都有哪些?
- 八种数据数据类型(int,short,long,double,byte,char,boolean,float);
- String,Class,注解类型,枚举类;
- 以上类型的数组形式;
5. 请问在什么情况下,使用“注解的属性”时,可以省略“属性名”?
- 如果注解中只有一个属性且名字叫 value,则在使用该注解时可以直接给该属性赋值,而不需要给出属性名;
b. 编程题
1. 模拟 JUnit 的 @Test 注解
- 见上文 7.c;
推荐阅读:Java 的反射技术 >>>