day14【反射和注解】
今日内容介绍
反射
类加载器
注解
能够通过反射技术获取Class字节码对象
能够通过反射技术获取构造方法对象,并创建对象
能够通过反射获取成员方法对象,并且调用方法
能够知道类加载器的作用
能够自定义注解X能够使用自定义注解
第一章 类加载器
1.1 类的加载时机
名词解释: 就是类什么时候进到内存了!!!你觉得 什么代码的运行就代表着类已经进入内存了!
类的加载时机
遵循 用的时候加载,不用不加载。
归纳总结为以下6点
1:创建类的对象。
2: 调用静态方法。
3: 访问静态变量。
4: 初始化某个子类。
5: java 命令 运行某个类的时候。
6: 使用反射形式 成功 java.lang.Class的数据(字节码文件对象)
package com.itheima.classloader;
public class Person {
//静态变量
public static int age=100;
//静态代码块 --如果执行类该类的静态代码块就证明了 该类一定被加载了
static{
System.out.println("Person的静态代码块执行了....");
}
package com.itheima.classloader;
public class Student extends Person{
static {
System.out.println("这是 Student的静态代码块...");
}
}
package com.itheima.classloader;
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException {
//类的加载时机
// 遵循 用的时候加载,不用不加载。
// 归纳总结为以下6点
// 1:创建类的对象。
// new Person();
// 2: 调用静态方法。
// Person.method();
// 3: 访问静态变量。
// int age = Person.age;
// 4: 初始化某个子类。
// new Student();
// 5: java 命令 运行某个类的时候。
// 6: 使用反射形式 成功 java.lang.Class的数据(字节码文件对象)
// 反射形式 获取字节码文件对象
Class clazz = Class.forName("com.itheima.classloader.Person");
}
}
//静态方法
public static void method(){
System.out.println("这里是Person类的静态方法");
}
}
类加载的过程
加载 链接 初始化
链接包含 验证 准备 解析
一个类的加载过程
1:类被使用到的时候,类文件加载到内存中,形成一个Class类型对象。
2:检验语法性,给静态成员开辟空间,解析有没有用到其他的类型,需要检测其他类型有没有在内存。(没在就加载)
3:完成了静态成员的初始化。(静态变量的赋值 完成静态代码块的执行 以及把静态方法放到内存上) .
2.2 类的加载器
类加载器:
作用:
负责把.class文件加载到内存中的方法区。
把class文件转换成了Class类型对象。(字节码对象)
研究一下 为什么 类能被加载 而且能保证加载一次!!双亲委派机制保证的。
类加载器组成:(不同的类加载器 是负责加载不同路径下的类的!!)
引导类加载器:BootstrapClassLoader
负责java核心类的加载 (System String Long Math...)
用c++写的。
扩展类加载器:ExtClassLoader
负责的是 jre扩展目录下的jar包的加载
也就是 jre lib 有个ext的目录
应用类加载器:AppClassLoader
负责的是 我们自己定义的类和第三方jar包中的类。
自定义类加载器:
加载你自己指定的一些类。
Person类 要被加载
Person类 要被加载
先去找应用类加载器询问应用类加载器--询问扩展类加载器有没有加载过--询问引导类加载器
引导类想加载--看一下路径是不是符合加载的路径 不是
扩展类加载器--看一下路径是不是符合 不是
引用类加载器--符合 执行加载
Person类有 String属性呢
询问应用类加载器--询问扩展类加载器有没有加载过--询问引导类加载器
引导类想加载--看一下路径是不是符合加载的路径 符合 它来加载 然后就结束了
Student
String
询问应用类加载器--询问扩展类加载器有没有加载过--询问引导类加载器 加载过 不加载
双亲委派机制的目的:保证类可以被加载且只能加载一次。
第三章 反射
3.1 Class 类的介绍
我们的字节码文件Xxx.class进入到内存中,就变成了一个 字节码文件对象,该对象的类型就是Class.
Class类型就是用来表示 字节码文件的一种类型,每个字节码文件都只有唯一的对应的 字节码文件对象。
哪这个字节码文件对象中有什么呢?
.java文件中写了什么字节码文件对象中就有什么!!
.java文件有什么?
成员变量
构造方法
成员方法
字节码文件对象中也是有
成员变量
构造方法---构造器
成员方法
只不过 它们的样子不是以前的样子了!!
以前 成员变量 private int age;---自己的格式样子
构造方法 public 类名(){}
成员方法 public void show(){}
在字节码文件中变样了,变成什么样子了呢?java面向对象语言。也就是你的成员变量,成员方法,构造方法都变成了对象形式了!!也就是 成员变量有自己的类型了!!!成员方法自己的类型了!!!构造方法也有自己的类型!!
3.2 获取字节码文件对象的方式
获取 class 对象方式 | 作用 | 应用场景 |
---|---|---|
Class.forName("全类名") | 通过指定的字符串路径获取 | 多用于配置文件,将类名定义在配置文件中。读取文件,加载类 |
类名.class | 通过类名的属性 class 获取 | 多用于参数的传递 |
对象.getClass() | 通过对象的 getClass()方法获取 | 多用于对象的获取字节码的方式 |
User
package com.itheima.classobject;
import java.util.Objects;
public class User {
private String name;
private int age;
public User() {
}
private User(String name) {
this.name = name;
}
public User(String name, int age) {
this.name = name;
this.age = 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 "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.itheima.refect01;
public class GetClassDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
//想玩反射得先获取 字节码对象
// 怎么获取有三种方式
//1:通过 Class类型的一个静态方法 Class forName("包名+类名")
// 这个方法是 根据包名+类名 找到类文件加载到 内存中 并返回该类型的字节码对象
// Class clazz = Class.forName("com.itheima.refect01.User");
//
// System.out.println(clazz);
//2: 通过类名.class 就是类的字节码
// 根据包名+类名 找到类文件 加载内存中 并返回该类型的字节码对象
// Class clazz = User.class;
// System.out.println(clazz);
// 3: 通过对象的.getClass()方法 当对象产生 字节码对象一定有了
// 对象.getClass() 获取对象运行时的类---在内存中就是字节码对象
User u = new User();
Class clazz= u.getClass();
System.out.println(clazz);
}
}
获取字节码文件对象三种形式
1:Class.forName("包名+类名") 用的最多 因为灵活
2: 类名.class 简单
3: 对象.getClass()
3.3 反射获取构造方法
Class 类中与 Constructor 相关方法
获取构造操作
package com.itheima.classobject;
import java.lang.reflect.Constructor;
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//先获取字节码文件对象
Class clazz = Class.forName("com.itheima.classobject.User");
// 1:通过字节码文件对象 获取该类下 所有的公共的构造方法
// 注意 现在的构造方法不再是一个格式了 而是对象形式。
Constructor[] constructors = clazz.getConstructors();
//这个数组有什么 ? 有该类的 所有公共构造
// for (Constructor constructor : constructors) {
// System.out.println(constructor);
// }
// 如果我们想要所有的构造,不管你是公共的还是私有的 忽略权限修饰符去寻找构造
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
// for (Constructor declaredConstructor : declaredConstructors) {
// System.out.println(declaredConstructor);
// }
//能不能获取指定的构造方法?
//可以 构造方法的名称就是类名,是不用写的,但是 构造是不是有多个 以参数区分
// 只能获取公共的指定的构造 获取的时候要传递 参数的类型!!
Constructor constructor = clazz.getConstructor(String.class, int.class);
System.out.println(constructor);
//可以找到私有的构造 同样也能找公共的
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
System.out.println(declaredConstructor);
}
}
Constructor 中构建对象的方法
反射创建对象---公共的无参的
package com.itheima.refect01;
import java.lang.reflect.Constructor;
public class ContructDemo02 {
//获取到 User类的 空参构造对象形式,然后 构建一个对象出来。
public static void main(String[] args) throws Exception {
// 1:获取字节码对象
Class clazz = Class.forName("com.itheima.refect01.User");
//2:获取User的空参构造 是公共的
Constructor con = clazz.getConstructor();
System.out.println(con);
//3: 有了 空参构造 Constructor con
// 根据构造 产生一个 User的实例
/*
这个方法就相当于 new 类名()
Object newInstance(Object... initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,
并用指定的初始化参数初始化该实例。
*/
Object o = con.newInstance();
// o 就是 一个 com.itheima.refect01.User 类型对应的一个对象
System.out.println(o);
}
}
反射创建对象---公共的有参的
package com.itheima.refect01;
import java.lang.reflect.Constructor;
public class ContructDemo03 {
//获取到 User类的 空参构造对象形式,然后 构建一个对象出来。
public static void main(String[] args) throws Exception {
// 1:获取字节码对象
Class clazz = Class.forName("com.itheima.refect01.User");
//2:获取User的构造 是公共的
Constructor con = clazz.getConstructor(String.class,int.class);
//字节码 去 "找" 不是执行 指定的构造方法
// 怎么找 这个参数是来帮我们找构造的
//我要找一个公共的构造 第一参数类型是String 第二个参数类型是int
System.out.println(con);
//3: 有了 带参构造 Constructor con 第一个参数String类型 第二个参数是int
// 根据构造 产生一个 User的实例
/*
这个方法就相当于
Object newInstance(Object... initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,
并用指定的初始化参数初始化该实例。
*/
Object o = con.newInstance("张三",18);//new User("张三",18)
// 通过反射的形式 调用指定构造 构建对象
System.out.println(o);
}
}
快捷的创建一个无参的对象
package com.itheima.refect01;
import java.lang.reflect.Constructor;
/*
Class中的方法
Object newInstance();
*/
public class ContructDemo04 {
//获取到 User类的 空参构造对象形式,然后 构建一个对象出来。
public static void main(String[] args) throws Exception {
// 1:获取字节码对象
Class clazz = Class.forName("com.itheima.refect01.User");
// //2:获取User的空参构造 是公共的
// Constructor con = clazz.getConstructor();
// System.out.println(con);
// //3: 有了 空参构造 Constructor con
// Object o = con.newInstance();
// System.out.println(o);
//上面两步合成一步 只针对空参构造 获取对象
// 先要找到空参构造 根据空参构造newInstance()
Object o = clazz.newInstance();
System.out.println(o);
}
}
反射私有构造创建对象
package com.itheima.refect01;
import java.lang.reflect.Constructor;
// 暴力反射: 获取到私有的内容 (放弃了权限的检查)可以直接完成调用。
public class ContructDemo05 {
//获取到 User类的 空参构造对象形式,然后 构建一个对象出来。
public static void main(String[] args) throws Exception {
// 1:获取字节码对象
Class clazz = Class.forName("com.itheima.refect01.User");
//2: 获取一个私有的构造 比如获取 参数类型是 String 哪个构造
Constructor con = clazz.getDeclaredConstructor(String.class);
/*
con代表 哪个 带有一个参数的构造
private User(String name) {
this.name = name;
}
当我们不做任何操作
私有的构造能找到 但是就不能执行
因为 该构造被 private修饰了
想不想 在反射状态下调用私有的东西
只要让 private 失效就可以
贿赂一下。放弃 权限修饰符的检查
在他们的父类中
public void setAccessible(boolean flag)
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
值为 false 则指示反射的对象应该实施 Java 语言访问检查。
*/
// 取消java的权限语法检查
con.setAccessible(true);
//3: 创建对象 反射形式 构建对象
Object o = con.newInstance("jack");
System.out.println(o);
}
}
总结
3.4 反射成员方法
package com.itheima.method;
/**
* 像这样的类 一般称为 javabean类 -- 毛豆
* 1:具有属性--一般私有的 就是说明不允许外界直接访问
* 2:提供 set get方法
* 3: 必须要有空参构造,可以拥有满参构造。
*/
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = 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;
}
// 如果不重写toString 出来的地址值 如果重写就会出现我们设置的格式展示
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void eat(){
System.out.println("用户吃土");
}
private void sleep(String address){
System.out.println("用户在"+address+"睡觉");
}
public String show(){
return "姓名"+name;
}
public int sum(int a,int b){
return a+b;
}
}
获取多个方法的测试
package com.itheima.reflect02;
import java.lang.reflect.Method;
public class MethodReflect01 {
/*
Method是方法的对象形式
Method[] getMethods()
获取所有public的方法,包括父类的。
Method[] getDeclaredMethods()
获取所有的方法,不包括父类定义了 只包含本类中。
Method getMethod(String methodName,T...argsType);
获取指定的方法
怎么指定呢?
必须要传名字
因为方法存在重载,
必须传递该方法的参数列表类型
*/
public static void main(String[] args) throws Exception{
// 方法对象怎么获取呢?
//1:获取字节码对象
Class clazz = Class.forName("com.itheima.reflect02.User");
//2.1 获取所有公共的 方法 包含父类
Method[] methods = clazz.getMethods();
System.out.println(methods.length);
for (Method method : methods) {
System.out.println(method);
}
System.out.println("======================");
// 获取自己定义的所有方法 不包含父类定义的
Method[] methods1 = clazz.getDeclaredMethods();
System.out.println(methods1.length);
for (Method method : methods1) {
System.out.println(method);
}
System.out.println("=================");
}
}
获取指定方法
package com.itheima.reflect02;
import java.lang.reflect.Method;
public class MethodReflect01 {
/*
Method是方法的对象形式
Method[] getMethods()
获取所有public的方法,包括父类的。
Method[] getDeclaredMethods()
获取所有的方法,不包括父类定义了 只包含本类中。
Method getMethod(String methodName,T...argsType);
获取指定的方法
怎么指定呢?
必须要传名字
因为方法存在重载,
必须传递该方法的参数列表类型
*/
public static void main(String[] args) throws Exception{
// 方法对象怎么获取呢?
//1:获取字节码对象
Class clazz = Class.forName("com.itheima.reflect02.User");
System.out.println("=================");
//如果获取的方法是 public void play() 空参的 无返回值的方法
Method play = clazz.getMethod("play");
System.out.println("找到的play方法:"+play);
// 如果方法是 public void com.itheima.reflect02.User.sleep(java.lang.String)
Method sleep = clazz.getMethod("sleep", String.class);
System.out.println("找到的sleep方法:"+sleep);
//如果方法是 public int com.itheima.reflect02.User.sum(int,int)
Method sum = clazz.getMethod("sum", int.class, int.class);
System.out.println("找到sum方法:"+sum);
//如果方法 private void com.itheima.reflect02.User.show()
Method show = clazz.getDeclaredMethod("show");
System.out.println("找到show方法:"+show);
}
}
反射获取成员方法并运行
package com.itheima.reflect02;
import java.lang.reflect.Method;
public class MethodReflect02 {
/*
现在已经找到 方法了 怎么执行呢?
*/
public static void main(String[] args) throws Exception{
//以前怎么执行方法
// User user = new User();
// user.play();
// user.sleep("张三");
// int sum1 = user.sum(2, 3);
// 方法对象怎么获取呢?
//1:获取字节码对象
Class clazz = Class.forName("com.itheima.reflect02.User");
//2:快捷的生成一个空参对象
Object o = clazz.newInstance();//多态形式
//如果获取的方法是 public void play() 空参的 无返回值的方法
Method play = clazz.getMethod("play");
System.out.println("找到的play方法:"+play);
//反射形式下 怎么 执行方法呢?
/*
Object invoke(Object obj, Object... args)
就是使用 方法对象形式 来执行方法
play.invoke()
第一个参数 就是对象 方法所属对象。就是咱们以前 user
第二个为什么是可变参数 代表的是 方法的参数值
user.sleep("张三")
user.sum(1,3)
*/
play.invoke(o);
// 如果方法是 public void com.itheima.reflect02.User.sleep(java.lang.String)
Method sleep = clazz.getMethod("sleep", String.class);
System.out.println("找到的sleep方法:"+sleep);
// 方法.invoke(对象,参数)
Object returnOb = sleep.invoke(o, "张小华");
System.out.println("sleep没有返回值:"+returnOb);
//如果方法是 public int com.itheima.reflect02.User.sum(int,int)
Method sum = clazz.getMethod("sum", int.class, int.class);
System.out.println("找到sum方法:"+sum);
Object retuenValue = sum.invoke(o, 2, 3);
// 方法反射执行之后 方法的返回值 会以返回值的形式的出现 只不过用Object
System.out.println("计算的结果:"+retuenValue);
//如果方法 private void com.itheima.reflect02.User.show()
Method show = clazz.getDeclaredMethod("show");
System.out.println("找到show方法:"+show);
//取消权限检查
show.setAccessible(true);
//怎么执行私有方法
show.invoke(o);
}
}
3.5 反射案例
需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
实现:
配置文件 usemethod.properties
propertiesclassName=com.itheima.pojo.Teacher methodName=teach
我们可以在 src 下创建该配置文件
反射
在你的代码中完成文件的读取,根据 className 的值 构建一个对象,根据 methodName 再调用指定方法。
需求:
读取 usemethod.properties
解析 键 className
键 methodName
className是咱们某个类的 权限定类名(包名+类名)
methodName 是方法名
然后根据 权限定类名 构建该类型对象
根据方法名 实现 反射调用
步骤:
将需要创建的对象的全类名和需要执行的方法定义在配置文件中
在程序中加载读取配置文件
使用反射技术来加载类文件进内存
创建对象
执行方法
javapackage com.itheima.usemethod; import java.io.FileNotFoundException; import java.io.FileReader; import java.lang.reflect.Method; import java.util.Properties; /** * 在模拟框架 * 我们把框架代码写好,之后配置文件中写什么, * 框架就能帮我调什么? * 意思就是 不改变 java代码的前提下,我们稍微修改配置文件 * 就可以实现不同方法的调用! */ public class Demo { public static void main(String[] args) throws Exception { // 1:解析配置文件 day15\\src\\usemethod.properties // 这种文件 怎么解析 使用 Properties集合 Properties pp = new Properties(); //2 里面有load(输入流) 完成数据读取到集合中 FileReader fr = new FileReader("day15\\src\\usemethod.properties"); pp.load(fr); //3 根据键找到指定的值 String className = pp.getProperty("className"); String methodName = pp.getProperty("methodName"); //4 根据className找到类 获取字节码对象 Class clazz = Class.forName(className); //5:找到指定的方法 Method method = clazz.getMethod(methodName); //6:反射执行之前 先获取 一个 实例 Object obj = clazz.newInstance(); //7:反射执行方法 method.invoke(obj); } }
读取优化
javapackage com.itheima.usemethod; import java.io.FileReader; import java.lang.reflect.Method; import java.util.Properties; import java.util.ResourceBundle; /** * 在模拟框架 * 我们把框架代码写好,之后配置文件中写什么, * 框架就能帮我调什么? * 意思就是 不改变 java代码的前提下,我们稍微修改配置文件 * 就可以实现不同方法的调用! */ public class Demo2 { public static void main(String[] args) throws Exception { // 1: 我们要求配置文件必须在src下 或者maven程序的resources下 // 这个类 是 来自 java.util.ResourceBundle // 怎么用 特别简单 你保证你的配置文件是.properties结尾,并且在src ResourceBundle bundle = ResourceBundle.getBundle("usemethod"); //3 根据键找到指定的值 String className = bundle.getString("className"); String methodName = bundle.getString("methodName"); //4 根据className找到类 获取字节码对象 Class clazz = Class.forName(className); //5:找到指定的方法 Method method = clazz.getMethod(methodName); //6:反射执行之前 先获取 一个 实例 Object obj = clazz.newInstance(); //7:反射执行方法 method.invoke(obj); } }
第四章 注解
4.1 注解概述
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
- 编写文档:通过代码里标识的注解生成文档【例如,生成文档 doc 文档】
- 代码分析:通过代码里标识的注解对代码进行分析【例如,注解的反射】
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override】
常见注解
- @author:用来标识作者名
- @version:用于标识对象的版本号,适用范围:文件、类、方法。
- @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。
4.2 自定义注解
定义格式
元注解
public @interface 注解名称{
属性列表;
}
注解本质上就是一个接口,该接口默认继承 Annotation 接口。
public @interface MyAnno extends java.lang.annotation.Annotation {}
任何一个注解,都默认的继承 Annotation 接口。
注解的属性
属性的作用
- 可以让用户在使用注解时传递参数,让注解的功能更加强大。
属性的格式
- 格式 1:数据类型 属性名();
- 格式 2:数据类型 属性名() default 默认值;
属性定义示例
javapublic @interface Student { String name(); // 姓名 int age() default 18; // 年龄 String gender() default "男"; // 性别 } // 该注解就有了三个属性:name,age,gender
属性适用的数据类型
- 八种基本数据类型(int,float,boolean,byte,double,char,long,short)。
- String 类型,Class 类型,枚举类型,注解类型。
- 以上所有类型的一维数组。
4.3 使用自定义注解
使用格式:
@注解名(属性名=属性值,属性名=属性值,属性名=属性值...)
定义注解
- 定义一个注解:Book
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 代码实现
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
使用注解
/**
* @author itheima
* @version 1.0
*/
public class BookShelf {
@Book(value = "西游记",price = 998,authors = {"吴承恩","白求恩"})
public void showBook(){
}
}
使用注意事项
- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
- 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
特殊属性 value
当注解中只有一个属性且名称是 value,在使用注解时给 value 属性赋值可以直接给属性值,无论 value 是单值元素还是数组类型。
2.如果注解中除了 value 属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,value 属性名不能省略。
4.4 注解之元注解----了解
默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方。如果要限制注解的使用位置怎么办?那就要学习一个新的知识点**:元注解**。
- @Target
- @Retention
元注解之@Target
- 作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
- 可选的参数值在枚举类ElemenetType中包括:
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
元注解之@Retention
- 作用:定义该注解的生命周期(有效范围)。
- 可选的参数值在枚举类型 RetentionPolicy 中包括
SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。
4.5 注解解析
通过 Java 技术获取注解数据的过程则称为注解解析。
与注解解析相关的接口
- Anontation:所有注解类型的公共接口,类似所有类的父类是 Object。
- AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个:
boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有指定的注解,有则返回true,否则返回false。**重点**
T getAnnotation(Class<T> annotationClass); 获得当前对象上指定的注解对象。
Annotation[] getAnnotations(); 获得当前对象及其从父类上继承的所有的注解对象。
Annotation[] getDeclaredAnnotations();获得当前对象上所有的注解对象,不包括父类的。
获取注解数据的原理
注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。
如注解作用在方法上,就通过方法(Method)对象得到它的注解。
java// 得到方法对象 Method method = clazz.getDeclaredMethod("方法名"); // 根据注解名得到方法上的注解对象 Book book = method.getAnnotation(Book.class);
如注解作用在类上,就通过 Class 对象得到它的注解。
// 获得Class对象 Class c = 类名.class; // 根据注解的Class获得使用在类上的注解对象 Book book = c.getAnnotation(Book.class);
4.6 模拟 Junit
案例分析
- 模拟 Junit 测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest 注解,编写三个方法,其中两个加上@MyTest 注解。
- 最后编写调用类,使用 main 方法调用目标类,模拟 Junit 的运行,只要有@MyTest 注释的方法都会运行。
注解 MyTest
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
目标类 Demo
package com.itheima.annotationtest;
public class Demo {
public void show1(){
System.out.println("我是show1");
}
@MyTest
public void show2(){
System.out.println("我是show2");
}
@MyTest
public void show3(){
System.out.println("我是show3");
}
}
调用类 JunitDemo
package com.itheima.annotationtest;
import java.lang.reflect.Method;
public class JunitDemo {
//程序入口
public static void main(String[] args) throws Exception{
//解析 Demo类 如何发现 它的方法上有@MyTest 就反射执行该方法
// 要解析 Demo 获取其字节码文件对象
Class clazz = Demo.class;
// 快捷方式创建对象
Object o = clazz.newInstance();
// 获取 Demo类中所有的方法
Method[] methods = clazz.getMethods();
//遍历得到每一个方法
for (Method method : methods) {
// 看该方法上 是否有指定的注解!!
boolean flag = method.isAnnotationPresent(MyTest.class);//看 该方法上有么有MyTest这个注解类型
if(flag==true){
//有 方法反射执行
method.invoke(o);
}
}
}
}