第十章 反射机制
前言
2024年最新版零基础Java笔记,最全最详细笔记,适用于零基础小白、软件设计师、就业、提升等,笔记会持续更新,共同进步,希望可以帮助更多的小伙伴!
感谢杜老师的超级无敌详细讲解,感谢动力节点,真的受益匪浅!第三次看老杜Java了,每次都有不一样的收获!由于老杜最新版Java没有md笔记,因此鄙人把所学所写的更新于此,共同进步,希望对大家有帮助!
笔记根据老杜《动力节点》视频进行编写,视频地址:动力节点Java零基础视频(下)
笔记有写的不好的地方,恳请在评论区指正批评,谢谢!
反射目录
10.1 反射机制概述
- 反射机制是JDK中的一套类库,这套类库可以帮助我们操作/读写class字节码文件。
- Java反射机制是指在运行时动态获取类的信息或动态调用对象的方法、修改属性等操作。
- 主要核心就是Class类、Constructor类、Field类、Method类等API。
- 反射机制主要应用于框架开发、动态代理、ORM框架、JDBC驱动等方面。
- 通过反射机制,程序员能够获得在编译期间不被知晓的类、属性、方法等信息。但是反射机制的性能较低,常常被认为是一种牺牲性能换取灵活性的实现方式。
- Java反射机制核心包:
java.lang.reflect.*
- Java反射机制核心类:
java.lang.Class
:Class类型的实例代表硬盘上的某个class文件。或者说代表某一种类型。java.lang.reflect.Field
:Filed类型的实例代表类中的属性/字段java.lang.reflect.Method
:类型的实例代表类中的方法java.lang.reflect.Constructor
:Constructor类型的实例代表类中的构造方法java.lang.reflect.Modifier
10.2 获取Class
Java中获取Class对象有以下三种方式:
- 调用Object类的getClass()方法
- 可以通过对象的getClass()方法来获取Class对象,例如:
Object obj = new Object(); Class clazz = obj.getClass();
- 注意:这个方法是通过引用去调用的。
- 使用“
类.class
”语法
可以使用“类.class”语法来获取Class对象,例如:
Class clazz = Object.class;
- 使用Class类的forName()方法
- 可以使用Class类的forName()方法来获取Class对象,例如:
Class clazz = Class.forName("java.lang.Object(完整的全限定类名)");
- 注意:
- 全限定类名是带有包名的
- 是lang包下的,
java.lang
也不能省略 - 这个是字符串参数
- 如果这个类根本不存在,运行时会报异常:
java.lang.ClassNotFoundException
- 这个方法的执行会导致类的加载动作的发生
10.3 反射作用的体现
-
通过IO流读取属性配置文件,获取类名,再通过反射机制实例化对象。(获取到Class类型的实例之后,可以实例化对象,通过反射机制实例化对象。)
- 使用
newInstance
方法实例化对象,底层实现原理:调用User类的无参数构造方法完成了对象的实例化。- 执行顺序:静态代码块→无参构造→main方法
- 要使用此方法,必须保证这个类中是存在无参构造方法,如果没有无参数构造方法,则会出现
NoSuchMethodException
public class ReflectTest{ public static void main(String[]args) throws Exception{ Class userClass = Class.forName("com.xfanny.javase.reflect"); User user = (User)userClass.newInstance(); System.out.println(user); } }
- 使用
-
在属性配置文件中配置类名:classInfo.properties
className=java.util.Date
/** * 读取属性配置文件,获取类名,通过反射机制实例化对象。 * 通过这个案例的演示就知道反射机制是灵活的。这个程序可以做到对象的动态创建。 * 只要修改属性配置文件就可以完成不同对象的实例化。 */ public class ReflectTest03 { public static void main(String[] args) throws Exception { // 资源绑定器 ResourceBundle bundle = ResourceBundle.getBundle("com.xfanny.javase.reflect.classInfo"); // 通过key获取value String className = bundle.getString("className"); // 通过反射机制实例化对象 Class classObj = Class.forName(className); // 实例化 Object obj = classObj.newInstance(); System.out.println(obj); } }
config.properties
className=com.xfanny.javase.reflect.UserService methodName=concat parameterTypes=java.lang.String,java.lang.String,java.lang.String parameterValues=aaa,bbb,ccc
如果要创建其他类的实例对象,只需要修改classInfo.properties配置文件即可。
这说明反射机制可以让程序变的更加灵活。在进行系统扩展时,可以达到OCP开闭原则。
10.4 反射Field
public class ReflectTest07 {
public static void main(String[] args) throws Exception{
// 如果不使用反射机制,怎么访问对象的属性?
Customer customer = new Customer();
// 修改属性的值(set动作)
// customer要素一
// name要素二
// "张三"要素三
customer.name = "张三";
// 读取属性的值(get动作)
// 读取哪个对象的哪个属性
System.out.println(customer.name);
// 如果使用反射机制,怎么访问对象的属性?
// 获取类
Class clazz = Class.forName("com.xfanny.javase.reflect.Customer");
// 获取对应的Field
Field ageField = clazz.getDeclaredField("age");
// 调用方法打破封装
ageField.setAccessible(true);
// 修改属性的值
// 给对象属性赋值三要素:给哪个对象 的 哪个属性 赋什么值
ageField.set(customer, 30);
// 读取属性的值
System.out.println("年龄:" + ageField.get(customer));
// 通过反射机制给name属性赋值,和读取name属性的值
Field nameField = clazz.getDeclaredField("name");
// 修改属性name的值
nameField.set(customer, "李四");
// 读取属性name的值
System.out.println(nameField.get(customer));
}
}
在属性配置文件中配置类名:classInfo.properties
className=java.util.Date
通过IO流读取属性配置文件,获取类名,再通过反射机制实例化对象。
如果要创建其他类的实例对象,只需要修改classInfo.properties配置文件即可。
这说明反射机制可以让程序变的更加灵活。在进行系统扩展时,可以达到OCP开闭原则。
10.5 反射Method
反射Method包括两方面:
-
一方面:通过反射机制获取Method
Method method = clazz.getDeclaredMethod("methodName", paramTypes);
-
另一方面:通过Method调用方法
Class clazz = MyClass.class;
Method method = clazz.getDeclaredMethod(“methodName”, paramTypes);
Object[] args = {arg1, arg2, arg3};
Object result = method.invoke(myObject, args);
```
代码:
public class ReflectTest10 {
public static void main(String[] args) throws Exception {
// 不使用反射机制怎么调用方法?
// 创建对象
UserService userService = new UserService();
// 调用方法
// 分析:调用一个方法需要几个要素?四要素
// 调用哪个对象的哪个方法,传什么参数,返回什么值
boolean isSuccess = userService.login("admin", "123456");
System.out.println(isSuccess ? "登录成功" : "登录失败");
// 调用方法
userService.logout();
// 通过反射机制调用login方法
// 获取Class
Class clazz = Class.forName("com.xfanny.javase.reflect.UserService");
// 获取login方法
Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);
// 调用login方法
Object retValue = loginMethod.invoke(userService, "admin", "123456");
System.out.println(retValue);
// 调用logout方法
Method logoutMethod = clazz.getDeclaredMethod("logout");
logoutMethod.invoke(userService);
}
}
10.6 反射Constructor
反射Constructor包括两方面:
- 一方面:通过反射机制获取Constructor
Constructor constructor2 = clazz.getDeclaredConstructor(paramTypes);
- 另一方面:通过Constructor创建对象
Class clazz = MyClass.class; Constructor constructor = clazz.getDeclaredConstructor(paramTypes); Object[] args = {arg1, arg2, arg3}; Object myObject = constructor.newInstance(args);
10.7 模拟框架的部分实现
- 配置文件中配置如下信息:classInfo.properties
className=com.xfanny.javase.reflect.UserService methodName=login parameterTypes=java.lang.String,java.lang.String parameterValues=admin,123456
- 通过反射机制创建对象,调用配置的方法
- 本案例的实现类似于Spring框架中部分实现,主要是通过修改配置文件来达到创建不同的对象,调用不同的方法。
public class ReflectTest13 {
public static void main(String[] args) throws Exception {
// 读取属性配置文件
ResourceBundle bundle = ResourceBundle.getBundle("com.xfanny.javase.reflect.config");
String className = bundle.getString("className");
String methodName = bundle.getString("methodName");
String parameterTypes = bundle.getString("parameterTypes");
String parameterValues = bundle.getString("parameterValues");
// 通过反射机制调用方法
// 创建对象(依赖无参数构造方法)
Class<?> clazz = Class.forName(className);
Constructor<?> defaultCon = clazz.getDeclaredConstructor();
Object obj = defaultCon.newInstance();
// 获取方法
// java.lang.String,java.lang.String
String[] strParameterTypes = parameterTypes.split(",");
Class[] classParameterTypes = new Class[strParameterTypes.length];
for (int i = 0; i < strParameterTypes.length; i++) {
classParameterTypes[i] = Class.forName(strParameterTypes[i]);
}
Method method = clazz.getDeclaredMethod(methodName, classParameterTypes);
// 调用方法
// parameterValues=admin,123456
Object retValue = method.invoke(obj, parameterValues.split(","));
System.out.println(retValue);
}
}
10.8 类加载及双亲委派机制
10.8.1 类加载过程
- 装载(loading)
- 类加载器负责将类的class文件读入内存,并创建一个java.lang.Class对象
- 连接(linking)
- 验证(Verify)
- 确保加载类的信息符合JVM规范。
- 准备(Prepare)
- 正式为静态变量在方法区中开辟存储空间并设置默认值
- public static int k = 10; 此时:k会赋值0
- public static final int f = 10; 此时: f会赋值10
- 解析(Resolve)
- 将虚拟机常量池内的符号引用替换为直接引用(地址)的过程。
- 验证(Verify)
- 初始化(initialization)
- 静态变量赋值,静态代码块执行
低版本的JDK中类加载器的名字:
启动类加载器:负责加载rt.jar
扩展类加载器:ext/*.jar
系统类加载器:classpath
10.8.2 获取Class的四种方式
- 静态方法
Class clazz = Class.forName(“全限定类名”)
- 实例方法
Class clazz = 引用.getClass();
- class属性
Class clazz = 类型名.class;
- 通过类加载器获取
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class clazz = classLoader.loadClass(“全限定类名”);
```
Class.forName和classLoader.loadClass()的区别?
- Class.forName():类加载时会进行初始化。
- classLoader.loadClass():类加载时不会进行初始化,直到第一次使用该类。
10.8.3 类加载器
- 虚拟机内部提供了三种类加载器(Java9+):
- 启动类加载器(BootstrapClassLoader):加载Java最核心的类,例如String
- 平台类加载器(PlatformClassLoader):加载Java平台扩展的类库,例如解析XML的
- 应用类加载器(AppClassLoader):加载classpath中的
- 同时我们还可以自定义一个类加载器(UserClassLoader)
- 获取类加载器可以通过
getParent()
方法一级一级获取
10.8.4 双亲委派机制
- 某个类加载器接收到加载类的任务时,通常委托给“父 类加载”完成加载。
- 最“父 类加载器”无法加载时,一级一级向下委托加载任务。
- 作用:
- 保护程序的安全。
- 防止类加载重复。
10.9 反射泛型(了解)
- 反射父类的泛型
- 反射接口的泛型
- 反射属性上的泛型
- 反射方法参数上的泛型
- 反射方法返回值的泛型
- 反射构造方法参数上的泛型