反射
一.类加载
概念:
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,VM将会连续完成故这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化
类的加载
就是指将class文件读入内存,并为之创建一个java.lang.Class对象任何类被使用时,系统都会为之建立一个java.lang.Class对象
类的连接
验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致准备阶段:负责为类的类变量分配内存,并设置默认初始化值
解析阶段:将类的二进制数据中的符号引用替换为直接引用
类的初始化
在该阶段,主要就是对类变量进行初始化
类初始化步骤:
- 假如类还未被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化其直接父类假如类中有初始语句,则系统依次执行这些初始化语句
- 注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化
类的初始化时机:
- 创建类的实例调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
二.类加载器
作用:
负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象。
机制:
- **全盘负责:**就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
- **父类委托:**就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
- 缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
ClassLoader:负责加载类对象
Java运行时具有以下内置类加载器:
-
Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null,并且没有父null
-
Platform class loader:平台类加载器可以看到所有平台类,平台类包括由平台类加载器或其祖先定其实现类和JDK特定的运行时类
-
System class loader:它也被称为应用程序类加载器,与平台类加载器不同。系统类加载器通常用模块路径和JDK特定工具上的类
-
类加载器的继承关系: System的父加载器为Platform,而Platform的父加载器为Bootstrap
public static void main(String[] args) {
//static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器
ClassLoader c = ClassLoader.getSystemClassLoader();
System.out.println(c); //AppClassLoader
//ClassLoader getParent():返回父类加载器进行委派
ClassLoader c2 = c.getParent();
System.out.println(c2); //ExtClassLoader
ClassLoader c3 = c2.getParent();
System.out.println(c3); //null
}
ClassLoader 中的两个方法:
方法 | 说明 |
---|---|
static ClassLoader getSystemClassLoader() | 返回用于委派的系统类加载器 |
ClassLoader getParent() | 返回父类加载器进行委派 |
*三.类加载器流程:
https://blog.csdn.net/m0_50370837/article/details/121226022
四.反射
概述:是指在运行时去获取一个类的变量和方法信息,然后通过获取得到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期扔可扩展。
【有两个不同的类,这两个类经过类加载器加载对应的.class文件,生成一个class类,类中包含成员变量、构造方法、成员方法……】
1.获取class类(字节码文件)的对象:
-
使用类的class属性来获取该类对应的Class对象。举例: Studentclass将会返回Student类对应的Class对象【最方便】
Class<Student> c1 = Student.class; System.out.println(c1); //输出:class wxy.test02.Student //一个类只有一个字节码对象
-
调用对象的getClass()方法,返回该对象所属类对应的Class对象【该方法是Object类中的方法,所有的Java对象都可以调用该方法】
Student s = new Student(); Class<? extends Student> c3 = s.getClass();
-
使用Class类中的静态方法forName(StringclassName),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径【路径随时修改,灵活性高】
Class<?> c4 = Class.forName("wxy.test02.Student"); //抛异常 //并不知字符串是哪个包下某给类,所以使用泛型通配符?
2.反射获取构造方法
Class类中用于获取构造方法的方法
- Constructor<?>[ ] getConstructors0:返回所有公共构造方法对象的数组
- Constructor<?>[ ] getDeclaredConstructors():返回所有构造方法对象的数组
- Constructor< T > getConstructor(Class <?> … parameterTypes):返回单个公共构造方法对象
- Constructor< T > getDeclaredConstructor(Class <?> … parameterTypes):返回单个构造方法对象
Constructor类中用于创建对象的方法
- T newInstance(Object… initargs):根据指定的构造方法创建对象
示例:
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("wxy.test02.Student");
System.out.println("----------------返回对象数组----------------");
//Constructor<?>[] getConstructors():返回一个包含Constructor对象的数组
//Constructor对象反应了由该Class对象表示的类的所有公共构造函数
Constructor<?>[] cons = c.getConstructors();
//Constructor<?>[] getDeclaredConstructors():返回所有构造函数的Constructor对象的数组
Constructor<?>[] cons2 = c.getDeclaredConstructors();
for(Constructor con : cons2){
System.out.println(con);
}
System.out.println("----------------返回单个对象----------------");
//gettConstructor(Class<?>…parameterTypes):返回一个Constructor对象,类的指定公共构造函数
//getDeclaredConstructors(Class<?>…parameterTypes):返回一个Constructor对象,类或接口的指定构造函数
//参数:你所要获得的构造方法的参数的个数和数据类型对应的字节码对象
Constructor<?> con = c.getConstructor();
//反射创建对象的方法:
//等同于:Student s = new Student(); syso(s);
Object obj = con.newInstance();
System.out.println(obj);
}
实例:
public static void main(String[] args) throws Exception {
//获取Class对象
Class<?> c = Class.forName("wxy.test02.Student");
/*
* 获取带三个参数的构造方法对象:
*/
Constructor<?> con = c.getConstructor(String.class, int.class, String.class);
//基本数据类型也可以通过.class得到对应的Class类型
//生成对象:
Object obj = con.newInstance("测试员1",22,"南阳");
System.out.println(obj);
System.out.println("-------------------------------------");
/*
* 私有一个带参构造方法:
*/
Constructor<?> con2 = c.getDeclaredConstructor(String.class);
//暴力反射: 【针对私有构造方法创建对象】
//public void setAccessible (boolean flag):值为true,取消访问检查
con2.setAccessible(true);
Object obj2 = con2.newInstance("测试员2");
System.out.println(obj2);
}
3.反射获取成员变量
Class类中用于获取成员变量的方法
- Field[] getFields():返回所有公共成员变量对象的数组
- Field[ getDeclaredFields():返回所有成员变量对象的数组
- Field getField(String name):返回单个公共成员变量对象
- Field getDeclaredField(String name):返回单个成员变量对象
Field类中用于给成员变量赋值的方法
- void set(Object obj, Object value):给obj对象的成员变量赋值为value
示例:
public static void main(String[] args) throws Exception {
//获取Class对象
Class<?> c = Class.forName("wxy.test02.Student");
System.out.println("--------数组---------");
//可访问的公有变量
// Field[] fileds = c.getFields();
//返回一个Field对象的数组,反映了由该Class对象表示的类或接口声明的所有字段
Field[] fileds = c.getDeclaredFields();
for(Field filed : fileds){
System.out.println(filed);
}
System.out.println("--------根据参数获取单个---------");
//Filed getFiled (String name) 返回一个Filed对象,指定的公有成员字段
//Filed getDeclaredField (String name) 返回一个Filed对象,指定的声明字段
Field add = c.getField("address");
//获取无参构造方法创建的对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
add.set(obj, "西安"); //给obj的成员变量add赋值西安
System.out.println(obj);
//Student s = new Student();
//s.address = "西安";
//System.out.println(s);
}
}
//首先获取Class对象c,然后通过c按照指定的成员变量得到成员变量对象,
//最后成员变量对象调用一个方法,看似是给对象obj赋值,实际是给成员变量赋值。
实例:
要求:
- Student s = new Student();
- s.name = “xxxx”;s.age = 22;s.address = "henan ";
- 输出s
/**
* 反射获取成员变量练习
* @author Lenovo
*
*/
public class LianxiTest02 {
public static void main(String[] args) throws Exception {
//获取Class对象
Class<?> c = Class.forName("wxy.test02.Student");
//创建无参构造方法创建对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
System.out.println(obj);
//赋值
Field namefield = c.getDeclaredField("name"); //name是私有变量
namefield.setAccessible(true); //暴力
namefield.set(obj,"三百七七");
System.out.println(obj);
Field agefield = c.getDeclaredField("age");
agefield.setAccessible(true); //暴力
agefield.set(obj,22);
System.out.println(obj);
Field addressfield = c.getField("address");
addressfield.set(obj,"武汉");
System.out.println(obj);
}
}
4.反射获取成员方法:
Class类中用于获取成员方法的方法
-
Method[ ] getMethods():返回所有公共成员方法对象的数组,包括继承的
-
Method[ ] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
-
Method getMethod(String name, Class<?>… parameterTypes):返回单个公共成员方法对象
Method m = c.getMethod("method1"); //没有参数就不写
-
Method getDeclaredMethod(String name, Class<?>… parameterTypes):返回单个成员方法对象
Method类中用于调用成员方法的方法
- Object invoke (Object obj,Object… args)在具有指定参数的指定对象上调用此方法对象表示的基础方法
Object:返回值类型
obj:调成员方法的对象
args:方法需要的参数
实例:
用反射实现获取方法:
要求:Student s = new Student();
s.method1();s.method2(“xxxx”);
String ss = s.method3(“xxxx”,3);System.out.println(ss);
s.function();
package wxy.test05;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 反射获取成员方法练习
* @author Lenovo
*
*/
public class LianxiTest03 {
public static void main(String[] args) throws Exception {
//获取Class对象
Class<?> c = Class.forName("wxy.test02.Student");
// Student s = new Student();
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
//s.method1()
Method m1 = c.getMethod("method1");
m1.invoke(obj);
//s.method2("xxxx")
Method m2 = c.getMethod("method2",String.class);
m2.invoke(obj,"XXXXX");
//String ss = s.method3("xxxx",3);System.out.println(ss);
Method m3 = c.getMethod("method3", String.class, int.class);
Object o = m3.invoke(obj,"XXXXX",3); //有返回值
System.out.println(o);
//s.function();私有方法、暴力
Method m4 = c.getDeclaredMethod("function");
m4.setAccessible(true);
m4.invoke(obj);
}
}
5.通过反射越过泛型检查:
实例:
有一个ArrayList< Integer >集合,现在在这个集合中添加一个字符串数据
public class ZongLianxi01 {
public static void main(String[] args) {
// 创建集合
ArrayList<Integer> array = new ArrayList<Integer>();
// array.add("xxx"); //集合泛型为数字类型,直接添加字符串无法使用
Class<? extends ArrayList> c= array.getClass();
try {
Method mm = c.getMethod("add", Object.class);
mm.invoke(array, "hello");
mm.invoke(array, "world");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(array);
}
}
6.通过反射运行配置文件内容:
实例:
有一个Student类中有study方法,有一个Teacher类中有teach方法
测试类调用:这种方法需要动测试类的代码,为了方便,写一个txt文件,在文件里面改,不需要动代码
Student s = new Student();
s.study();
Teacher t = new Teacher();
t.teach();
<!-- .txt文件 -->
className = wxy.Student //文件路径改Studetn或者Teacher或者其他
methodName = study //方法名
main方法:
/**
* 通过配置文件运行类中的方法
* 只需要该配置文件中的名字,指定运行某个类的方法
* @author Lenovo
*
*/
public class ZongLianxi02 {
public static void main(String[] args) throws Exception {
/*
class.txt中定义:
className=xxx
methodName=xxx
*/
//加载数据
Properties prop = new Properties(); //获取配置文件的对象
FileReader fr = new FileReader("class.txt");
prop.load(fr);
fr.close();
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//通过反射使用
Class<?> c= Class.forName(className); //com.Student
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
Method m = c.getMethod(methodName); //study
m.invoke(obj);
}
}
7.反射main方法
在Student类中有mian方法:
public class Student {
public static void main(String[] args) {
System.out.println("Student中的main方法执行了。。。");
}
}
在测试类中
methodMain.invoke(null, new String[]{“a”,“b”,“c”});调用main方法,{里面任意}
▶第一个参数,对象类型,因为方法是static静态的,所以为null可以,
▶第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
/**
* 获取Student类的main方法,不要与当前的main方法搞混
*/
public class Test {
public static void main(String[] args) {
try {
//1、获取Student对象的字节码
Class c = Class.forName("fanshe.main.Student");
//2、获取main方法
//第一个参数:方法名称,第二个参数:方法形参的类型,
Method methodMain = c.getMethod("main", String[].class);
//3、调用main方法
// methodMain.invoke(null, new String[]{"","",""});
methodMain.invoke(null, (Object)new String[]{});//方式一
// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
} catch (Exception e) {
e.printStackTrace();
}
}
}