目录
一、概念
1、反射是什么
- 作用:反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,包括成员变量、构造器、成员方法等,并能操作对象的属性及方法。
- 功能:动态获取信息以及动态调用对象方法
- 用途:在设计模式或者框架底层都会用到
- 个人理解:之前都是通过new关键字创建对象,进而使用方法。现在是直接通过大Class实例来完成对象创建、属性复制、方法调用这一系列过程
2、绘制 内存模型图 来解释 反射机制
3、反射机制 相关的类
- java.lang.Class:Class代表类,由启动类加载器进行加载;某个类被类加载器加载后,将在堆中生成一个Class对象,该对象和方法区中的二进制字节码文件存在联系
- java.lang.reflect.Method:代表类的方法,Method对象代表类中某个方法
- java.lang.reflect.Field:代表类的成员变量,Field对象代表类中某个属性
- java.lang.reflect.Constructctor:代表类的构造方法,Constructctor对象代表类中某个构造器
4、反射的优点和缺点
- 优点:可以动态的创建和使用对象,其应用于框架底层,如果没有反射机制,框架技术就失去底层支撑
- 缺点:使用反射基本是解释执行,对执行速度有影响(对于普通代码来说,现在JVM编辑器采用热点代码即时编译 + 解释执行两种方式并行的方案)
证明反射缺点:
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
int num = 100000000;
System.out.println("执行方法次数:" + num);
m1(num);
m2(num);
}
/**
* 通过普通方式执行方法
*
* @author 明快de玄米61
* @date 2024/7/22 23:10
* @param num 执行次数
**/
public static void m1(int num) {
// 开始时间
long startTime = System.currentTimeMillis();
// 执行代码
Cat cat = new Cat();
for (int i = 0; i < num; i++) {
cat.said();
}
// 结束时间
long endTime = System.currentTimeMillis();
// 耗时
System.out.println("普通方式耗时:" + (endTime - startTime) + "毫秒");
}
/**
* 通过反射方式执行方法
*
* @author 明快de玄米61
* @date 2024/7/22 21:45
* @param num 执行次数
**/
public static void m2(int num) throws Exception {
// 开始时间
long startTime = System.currentTimeMillis();
// 执行代码
Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
Object o = cls.newInstance();
Method method = cls.getMethod("said");
for (int i = 0; i < num; i++) {
method.invoke(o);
}
// 结束时间
long endTime = System.currentTimeMillis();
// 耗时
System.out.println("反射方式耗时:" + (endTime - startTime) + "毫秒");
}
}
class Cat {
public void said() {
// 控制台会出现太多打印结果,所以注释掉吧!
// System.out.println("喵喵喵~");
}
}
证明反射缺点的结果:
执行方法次数:100000000
普通方式耗时:9毫秒
反射方式耗时:775毫秒
5、反射调用优化—关闭访问检查
Method、Field、Constructor对象都有setAccessible()方法
该方法作用是禁用访问安全检查的开关,默认是关闭的。即:默认开启访问检查
如果参数设置为true
,表示反射的对象在使用时取消访问检查,提高反射的效率
如果参数设置为false
(默认值),表示反射的对象将执行访问检查
举例说明:
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
int num = 100000000;
System.out.println("执行方法次数:" + num);
m2(num);
m3(num);
}
/**
* 通过反射方式执行方法(执行访问检查:默认值)
*
* @author 明快de玄米61
* @date 2024/7/22 21:45
* @param num 执行次数
**/
public static void m2(int num) throws Exception {
// 开始时间
long startTime = System.currentTimeMillis();
// 执行代码
Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
Object o = cls.newInstance();
Method method = cls.getMethod("said");
for (int i = 0; i < num; i++) {
method.invoke(o);
}
// 结束时间
long endTime = System.currentTimeMillis();
// 耗时
System.out.println("在反射时,执行访问检查的耗时:" + (endTime - startTime) + "毫秒");
}
/**
* 通过反射方式执行方法(不执行访问检查:手动设置)
*
* @author 明快de玄米61
* @date 2024/7/22 21:45
* @param num 执行次数
**/
public static void m3(int num) throws Exception {
// 开始时间
long startTime = System.currentTimeMillis();
// 执行代码
Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
Object o = cls.newInstance();
Method method = cls.getMethod("said");
// 在反射调用方法时,取消访问检查
method.setAccessible(true);
for (int i = 0; i < num; i++) {
method.invoke(o);
}
// 结束时间
long endTime = System.currentTimeMillis();
// 耗时
System.out.println("在反射时,取消访问检查的耗时:" + (endTime - startTime) + "毫秒");
}
}
class Cat {
public void said() {
// 控制台会出现太多打印结果,所以注释掉吧!
// System.out.println("喵喵喵~");
}
}
结果:
执行方法次数:100000000
在反射时,执行访问检查的耗时:243毫秒
在反射时,取消访问检查的耗时:125毫秒
6、Class类 介绍
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是由类加载器加载的,通过ClassLoader类以及子类的loadClass方法
- 对于某个类的Class类对象,在内存中只有一份,根据双亲委派机制,所以类只加载一次
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整地得到一个类的完整结构,可以通过一系列API得到属性、方法、构造器
- Class对象存放在堆中
- 类的字节码二进制数据存放在方法区中,有的地方称为类的元数据(包括方法代码、变量名、方法名、访问权限等等)
7、Class类—常用方法
8、哪些类型有Class对象
- 类:外部类、成员内部类、静态内部类、局部内部类、匿名内部类
- 接口
- 枚举
- 注解
- 数组
- 基本数据类型
- void
示例代码如下:
// 外部类
Class<String> cls1 = String.class;
// 接口
Class<Serializable> cls2 = Serializable.class;
// 枚举
Class<Thread.State> cls3 = Thread.State.class;
// 注解
Class<Override> cls4 = Override.class;
// 数组
// 一维数组
Class<int[][]> cls5 = int[][].class;
// 二维数组
Class<Integer[]> cls6 = Integer[].class;
// 基本数组类型
Class<Integer> cls7 = int.class;
// void
Class<Void> cls8 = void.class;
二、使用方式
1、Class类
前提准备:
import java.lang.annotation.Annotation;
// 父类
class Animal {
}
// 子类
class Cat extends Animal {
public void said() {
}
}
示例:
// 1、获取Class对象
// 全类名
String classPath = "com.atguigu.reflect.Cat";
// 通过全类名获取Class类对象
Class<?> cls = Class.forName(classPath);
// 2、获取全类名,结果是:Cat
System.out.println(cls.getName());
// 3、获取简单类名,结果是:com.atguigu.reflect.Cat
System.out.