目录
一、概念
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.println(cls.getSimpleName());
// 4、获取Class对象所属的Class类,承接以上代码,结果是:class java.lang.Class
System.out.println(cls.getClass().toString());
// 5、获取包名,承接以上代码,结果是:com.atguigu.reflect
System.out.println(cls.getPackage().getName());
// 6、返回父类全路径名称,承接以上代码,结果是:com.atguigu.reflect.Animal
System.out.println(cls.getSuperclass().getName());
// 7、获取Class对象的方式
// 方式1:知道类的全路径,多用于通过配置文件内容来加载类
Class<?> cls = Class.forName(classPath);
// 方式2:知道具体的类,多用于参数传递,比如通过反射得到类的构造器对象
Class<Cat> cls = Cat.class;
// 方式3:知道创建好的对象
Class<? extends Cat> cls = cat.getClass();
// 方法4:通过类加载器,但是两者需要使用同一个类加载器
ClassLoader classLoader = Test.class.getClassLoader();
Class<?> aClass = classLoader.loadClass("com.atguigu.reflect.Cat");
// 方式5:获取基本数据类型的Class对象(boolean、byte、char、short、int、long、float、double)
// 结果:int
Class<Integer> cls = int.class;
System.out.println(cls);
// 方式6:获取基本类型对应包装类的Class对象,其实方法5和方法6得到的对应类型的Class对象是同一个
// 结果:int
Class<Integer> cls = Integer.TYPE;
System.out.println(cls);
2、接口
代码:
import java.lang.annotation.Annotation;
public class Test {
public static void main(String[] args) throws Exception {
// 返回直接实现的接口信息,不返回接口的父级接口信息
Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
for (Class<?> ifs : cls.getInterfaces()) {
System.out.println(ifs.getName());
}
}
}
// 父接口
interface Type {}
// 子接口
interface Type1 extends Type {}
interface Type2 extends Type {}
// 类
class Cat implements Type1, Type2 {
}
结果:
com.atguigu.reflect.Type1
com.atguigu.reflect.Type2
3、注解
代码:
import java.lang.annotation.Annotation;
public class Test {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
for (Annotation annotation : cls.getAnnotations()) {
System.out.println(annotation.toString());
}
}
}
// 类
@Deprecated
class Cat {
}
结果:
@java.lang.Deprecated()
4、字段
代码:
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
// 1、返回 “所有可访问的公共字段类对象” 数组
// 其中包括:当前类 / 接口、超类、超级接口中的公共字段
System.out.println("1、返回 “所有可访问的公共字段类对象” 数组");
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
// 2、返回 “当前类或接口声明的所有字段类对象” 数组
// 其中包括:公共,受保护,默认(包)访问和私有字段,但不包括继承或者实现的
System.out.println("\n2、返回 “当前类或接口声明的所有字段类对象” 数组");
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field.getName());
}
// 3、打印字段名称、字段修饰符、字段类型
// 字段修饰符说明:默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16),其中getModifiers()方法返回值是所有修饰符之和
System.out.println("\n3、打印字段名称、字段修饰符、字段类型");
for (Field field : declaredFields) {
System.out.printf("字段名称:%s,字段修饰符:%s,字段类型:%s\n", field.getName(), field.getModifiers(), field.getType().getName());
}
// 4、设置公共字段的值
System.out.println("\n4、设置公共字段的值");
Cat cat = new Cat(1.0, 2.0, 3.0);
// 使用getField方法获取字段Field对象,该方法仅支持公共(public)字段哦~
// Field heightField = cls1.getField("height");
// 使用getDeclaredField方法调用构造器,该方法支持所有构造器哦~,这两种方式都是可以的
Field heightField = cls.getDeclaredField("height");
heightField.set(cat, 2.5);
System.out.println("height:" + cat.height);
// 5、设置非公共字段的值
System.out.println("\n5、设置非公共字段的值");
Field weightField = cls.getDeclaredField("weight");
// 如果不对私有属性字段进行爆破,直接调用set方法,将会抛出:
// Exception in thread "main" java.lang.IllegalAccessException: Class com.atguigu.reflect.Test can not access a member of class com.atguigu.reflect.Cat with modifiers "private"
weightField.setAccessible(true);
weightField.set(cat, 0.5);
System.out.println("weight:" + cat.getWeight());
// 6、获取公共字段的值
System.out.println("\n6、获取公共字段的值");
System.out.println("height:" + heightField.get(cat));
// 7、获取非公共字段的值
System.out.println("\n7、获取非公共字段的值");
// 如果方法没有提供获取值的方法(例如:getWeight方法),我们完全可以使用反射去完成获取字段值的流程
// 在上面已经通过“weightField.setAccessible(true)”语句完成了字段爆破过程,所以这里可以直接使用,不用爆破了
System.out.println("weight:" + weightField.get(cat));
// 8、设置以及获取非公共静态字段的值
System.out.println("\n8、设置以及获取非公共静态字段的值");
// 如果方法没有提供获取值的方法(例如:getWeight方法),我们完全可以使用反射去完成获取字段值的流程
Field lengthField = cls.getDeclaredField("length");
// 对私有字段进行操作的时候,需要先使用 “setAccessible(true)” 方法进行爆破~
lengthField.setAccessible(true);
// 静态字段是属于类的,所以get()方法、set()方法都不需要指明操作对象
lengthField.set(null, 3.5);
System.out.println("length:" + lengthField.get(null));
}
}
// 接口
interface Type {
// 公共属性
String belong = "动物科";
}
// 超类
class Animal {
// 公共属性
public String name;
// 私有属性
private int age;
}
// 子类
class Cat extends Animal implements Type {
// 公共属性
public double height;
// 私有属性
private double weight;
// 私有静态属性
private static double length;
public Cat(double height, double weight, double length) {
this.height = height;
this.weight = weight;
Cat.length = length;
}
public double getHeight() {
return height;
}
public double getWeight() {
return weight;
}
public static double getLength() {
return length;
}
}
结果:
1、返回 “所有可访问的公共字段类对象” 数组
height
belong
name
2、返回 “当前类或接口声明的所有字段类对象” 数组
height
weight
length
3、打印字段名称、字段修饰符、字段类型
字段名称:height,字段修饰符:1,字段类型:double
字段名称:weight,字段修饰符:2,字段类型:double
字段名称:length,字段修饰符:10,字段类型:double
4、设置公共字段的值
height:2.5
5、设置非公共字段的值
weight:0.5
6、获取公共字段的值
height:2.5
7、获取非公共字段的值
weight:0.5
8、获取非公共静态字段的值
length:3.5
5、方法
代码:
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
// 1、返回 “所有公共方法类对象” 数组
// 其中包括:当前类 / 接口、超类、超级接口中的公共方法
System.out.println("1、返回 “所有公共方法类对象” 数组");
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
// 2、返回 “当前类或接口声明的所有方法对象” 数组
// 其中包括:公共,受保护,默认(包)访问和私有方法,但不包括继承或者实现的
System.out.println("\n2、返回 “当前类或接口声明的所有方法对象” 数组");
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.getName());
}
// 3、打印方法名称、方法修饰符、方法参数数组、方法返回类型
// 方法修饰符说明:默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16),其中getModifiers()方法返回值是所有修饰符之和
System.out.println("\n3、打印方法名称、方法修饰符、方法参数数组、方法返回类型");
for (Method method : declaredMethods) {
System.out.printf("方法名称:%s,方法修饰符:%s,方法返回类型:%s\n", method.getName(), method.getModifiers(), method.getReturnType().getName());
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println(">>>>该方法的参数类型:" + parameterType.getName());
}
}
// 4、调用无参公共方法
System.out.println("\n4、调用无参公共方法");
Cat cat = new Cat();
// 使用getMethod方法获取Method对象,该方法仅支持公共(public)方法哦~
Method m4 = cls.getMethod("m4");
// 使用getDeclaredMethod方法获取Method对象,该方法支持所有方法哦~,这两种方式都是可以的
// Method m4 = cls.getDeclaredMethod("m4");
m4.invoke(cat);
// 5、调用有参私有方法
System.out.println("\n5、调用有参私有方法");
Method m5 = cls.getDeclaredMethod("m5", String.class, int.class);
// 如果不对私有方法进行爆破,直接调用invoke方法,将会抛出:
// Exception in thread "main" java.lang.IllegalAccessException: Class com.atguigu.reflect.Test can not access a member of class com.atguigu.reflect.Cat with modifiers "private"
m5.setAccessible(true);
Object result = m5.invoke(cat, "世界", 1);
System.out.println("方法调用结果:" + result);
System.out.println("方法返回结果类型未改变:" + (result.getClass() == String.class));
// 6、调用静态方法
System.out.println("\n6、调用静态方法");
Method m6 = cls.getDeclaredMethod("m6");
// 静态方法属于类,所以不用指定具体对象
m6.invoke(null);
}
}
// 接口
interface Type {
default void m1() {
}
}
// 超类
class Animal {
public void m2() {
}
private void m3() {
}
}
// 子类
class Cat extends Animal implements Type {
public void m4() {
System.out.println("注意:方法m4()被调用了");
}
private String m5(String a, int b) {
System.out.println("注意:方法m5(String a, int b)被调用了");
return "hello world~";
}
public static void m6() {
System.out.println("注意:静态方法m6()被调用了");
}
}
结果:
1、返回 “所有公共方法类对象” 数组
m4
m6
m2
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
m1
2、返回 “当前类或接口声明的所有方法对象” 数组
m4
m5
m6
3、打印方法名称、方法修饰符、方法参数数组、方法返回类型
方法名称:m4,方法修饰符:1,方法返回类型:void
方法名称:m5,方法修饰符:2,方法返回类型:java.lang.String
>>>>该方法的参数类型:java.lang.String
>>>>该方法的参数类型:int
方法名称:m6,方法修饰符:9,方法返回类型:void
4、调用无参公共方法
注意:方法m4()被调用了
5、调用有参私有方法
注意:方法m5(String a, int b)被调用了
方法调用结果:hello world~
方法返回结果类型未改变:true
6、调用静态方法
注意:静态方法m6()被调用了
6、构造器
代码:
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
// 1、返回 “类的所有公共构造函数” 数组
System.out.println("1、返回 “类的所有公共构造函数” 数组");
Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName());
}
// 2、返回 “类的所有构造函数” 数组
System.out.println("\n2、返回 “类的所有构造函数” 数组");
Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
System.out.println(constructor.getName());
}
// 3、打印构造器的参数数组
System.out.println("\n3、打印构造器的参数数组");
for (Constructor<?> constructor : declaredConstructors) {
System.out.println(">>>构造器名称:" + constructor.getName());
Class<?>[] parameterTypes = constructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println(">>>>>>参数名称:" + parameterType.getName());
}
}
// 4、创建无参构造对象
// 前提:存在公共的无参构造方法
System.out.println("\n4、创建公共无参构造对象");
// 这种创建方式,快速方便快捷
Object o = cls.newInstance();
System.out.println(o);
// 5、创建公共有参构造对象
System.out.println("\n5、创建公共有参构造对象");
// 使用getConstructor方法调用构造器,该方法仅支持公共(public)构造器哦~
// Constructor<?> constructor1 = cls.getConstructor(double.class, double.class);
// 使用getDeclaredConstructor方法调用构造器,该方法支持所有构造器哦~,这两种方式都是可以的
Constructor<?> constructor1 = cls.getDeclaredConstructor(double.class, double.class);
Object o1 = constructor1.newInstance(1.0, 2.0);
System.out.println(o1);
// 6、创建私有有参构造对象
System.out.println("\n6、创建私有有参构造对象");
// 必须使用getDeclaredConstructor()方法,才能获取私有构造器,而getConstructor方法仅支持公共(public)构造器哦~
Constructor<?> constructor2 = cls.getDeclaredConstructor(double.class);
// 如果不对私有构造器进行爆破,直接调用newInstance方法,将会抛出:
// Exception in thread "main" java.lang.IllegalAccessException: Class com.atguigu.reflect.Test can not access a member of class com.atguigu.reflect.Cat with modifiers "private"
constructor2.setAccessible(true);
Object o2 = constructor2.newInstance(1.0);
System.out.println(o2);
}
}
// 普通类
class Cat {
private double width;
private double height;
public Cat() {}
public Cat(double width, double height) {
this.width = width;
this.height = height;
}
private Cat(double height) {
this.height = height;
}
}
结果:
1、返回 “类的所有公共构造函数” 数组
com.atguigu.reflect.Cat
com.atguigu.reflect.Cat
2、返回 “类的所有构造函数” 数组
com.atguigu.reflect.Cat
com.atguigu.reflect.Cat
com.atguigu.reflect.Cat
3、打印构造器的参数数组
>>>构造器名称:com.atguigu.reflect.Cat
>>>构造器名称:com.atguigu.reflect.Cat
>>>>>>参数名称:double
>>>>>>参数名称:double
>>>构造器名称:com.atguigu.reflect.Cat
>>>>>>参数名称:double
4、创建公共无参构造对象
com.atguigu.reflect.Cat@4517d9a3
5、创建公共有参构造对象
com.atguigu.reflect.Cat@372f7a8d
6、创建私有有参构造对象
com.atguigu.reflect.Cat@2f92e0f4
三、思考拓展
- 针对构造器、方法、字段来说,普通方法可以获取当前类、父类、接口中的公共信息;而调用添加Declared的方法可以获取当前类/接口的所有信息,另外通过“setAccessible(true)”方法进行爆破之后,还能对私有静态构造器、方法、字段进行相关操作;普通方法以及添加Declared的方法各有千秋,可以根据不同场景使用不同的方法
- 针对不懂的属性字段或者方法,大家可以去查阅官方文档,这里给大家提供jdk1.8的API文档
链接:https://pan.baidu.com/s/1KlwQrBpAcmoZA-QZiC6gCw?pwd=gyg1
提取码:gyg1