目录
类加载器
01类的加载
02类的加载时机
03三种类的加载器
反射
01反射的概念以及作用
02class文件的产生过程
03获取class文件对象三种方式
04反射获取空参数构造方法并运行
05反射获取有参数的构造方法并运行
06反射获取构造方法并运行的快速的方式
07反射获取私有构造方法并运行
08反射获取成员变量并改值
09反射获取空参数成员方法并运行
10反射获取有参数的成员方法并运行
11反射泛型擦除
12反射通过配置文件运行的步骤
13反射通过配置文件运行功能实现
一、类加载器
* A.类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
* a 加载
* 就是指将class文件读入内存,并为之创建一个Class对象。
* 任何类被使用时系统都会建立一个Class对象
* b 连接
* 验证 是否有正确的内部结构,并和其他类协调一致
* 准备 负责为类的静态成员分配内存,并设置默认初始化值
* 解析 将类的二进制数据中的符号引用替换为直接引用
· * c 初始化
* 就是我们以前讲过的初始化步骤(new 对象)
* 注:简单的说就是:把.class文件加载到内存里,并把这个.class文件封装成一个Class类型的对象。
* B.类的加载时机
以下的情况,会加载这个类。
* a. 创建类的实例
* b. 类的静态变量,或者为静态变量赋值
* c. 类的静态方法
* d. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
* e. 初始化某个类的子类
* f. 直接使用java.exe命令来运行某个主类
* C: 类加载器(了解)
负责将.class文件加载到内在中,并为之生成对应的Class对象。
* a. Bootstrap ClassLoader 根类加载器
* 也被称为引导类加载器,负责Java核心类的加载
* 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
* b. Extension ClassLoader 扩展类加载器
* 负责JRE的扩展目录中jar包的加载。
* 在JDK中JRE的lib目录下ext目录
* c. System ClassLoader 系统类加载器
* 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
* 我们用的是System ClassLoader 系统类加载器
三、反射机制原理及示例
A. 反射定义
a. JAVA反射机制是在运行状态中,
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
b.反射技术
条件:运行状态
已知:一个类或一个对象(根本是已知.class文件)
结果:得到这个类或对象的所有方法和属性
注: 要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
B. Class类
a. Class类及Class对象的了解
要想解剖一个类,必须先了解Class对象。
阅读API的Class类得知,Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
b. 得到Class对象的三个方法
方式1: 通过Object类中的getClass()方法
Person person = new Person();
Class clazz = person.getClass();
方式2: 通过 类名.class 获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种方式简单)。
Class clazz = Person.class;
方式3: 通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可)。
Class c3 = Class.forName(“Person”);
注:第三种和前两种的区别是:
前两种你必须明确Person类型。后面是指定这种类型的字符串就行.这种扩展更强.我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了
得到Class对象的三个方法代码演示:测试类ReflectDemo .java
/*
* 获取.class字节码文件对象的方式
* 1:通过Object类中的getObject()方法
* 2: 通过 类名.class 获取到字节码文件对象
* 3: 反射中的方法,
* public static Class<?> forName(String className) throws ClassNotFoundException
* 返回与带有给定字符串名的类或接口相关联的 Class 对象
*/
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 1: 通过Object类中的getObject()方法
// Person p1 = new Person();
// Class c1 = p1.getClass();
// System.out.println("c1 = "+ c1);
// 2: 通过 类名.class 获取到字节码文件对象
// Class c2 = Person.class;
// System.out.println("c2 = "+ c2);
// 3: 反射中的方法
Class c3 = Class.forName("net.xikee_reflect.Person");// 包名.类名,获得字节码文件对象
System.out.println("c3 = " + c3);
}
}
注: Class类型的唯一性
因为一个.class文件在内存里只生成一个Class对象,所以无论那一种方法得到Class对象,得到的都是同一个对象。
C.通过反射获取无参构造方法并使用
通过调用newInstance()方法运行构造方法,得到该类的对象。
//通过Person类的全限定名,获取该类字节码文件对象
Class c = Class.forName("cn.itcast.demo1.Person");
//使用class文件对象,获取指定的构造方法,空参数的构造方法
Constructor con = c.getConstructor();
//运行空参数构造方法,Constructor类方法 newInstance()运行获取到的构造方法
//运行构造方法相当于创建Person类的对象
Object obj = con.newInstance();
D. 通过反射获取有参构造方法并使用
/*
* 通过反射,获取有参数的构造方法并运行
* 方法getConstructor,传递可以构造方法相对应的参数列表即可
*/
Class c = Class.forName("cn.itcast.demo1.Person");
//获取带有,String和int参数的构造方法
//String.class:获得String类型的字节码文件对象
Constructor con = c.getConstructor(String.class,int.class);
//运行构造方法, 运行构造方法后,传递的实际参数
Object obj = con.newInstance("张三",20);
System.out.println(obj.toString());
E. 通过反射获取无参构造方法并使用快捷方式
a. 使用的前提
类有空参的公共构造方法。(如果是同包,默认权限也可以)
b. 使用的基础
Class类的 public T newInstance() 方法
创建此 Class 对象所表示的类的一个新实例。
Class c = Class.forName("cn.itcast.demo1.Person");
// Class类中定义方法, T newInstance() 直接创建被反射类的对象实例
Object obj = c.newInstance();
F. 通过反射获取私有构造方法并使用
Class c = Class.forName("cn.itcast.demo1.Person");
Constructor con = c.getDeclaredConstructor(int.class,String.class);
//Constructor类,父类AccessibleObject,定义方法setAccessible(boolean b)
con.setAccessible(true);
Object obj = con.newInstance(18,"lisi");
注:不推荐,破坏了程序的封装性,安全性。
G. 反射获取成员变量并改值
//获取指定的成员变量 String name
//Class类的方法 Field getField(传递字符串类型的变量名) 获取指定的成员变量
Field field = c.getField("name");
//Field类的方法 void set(Object obj, Object value) ,修改成员变量的值
//Object obj 必须有对象的支持, Object value 修改后的值
field.set(obj,"xikee");
H. 反射获取空参数成员方法并运行
//获取指定的方法eat运行
// Method getMethod(String methodName,Class...c)
// methodName获取的方法名 c 方法的参数列表
Method method = c.getMethod("eat");
//使用Method类中的方法,运行获取到的方法eat
//Object invoke(Object obj, Object...o),参数:对象和相关参数(无参可忽略)
method.invoke(obj);
I. 反射获取有参数成员方法并运行
//调用Class类的方法getMethod获取指定的方法sleep
Method method = c.getMethod("sleep", String.class,int.class,double.class);
//调用Method类的方法invoke运行sleep方法
method.invoke(obj, "休眠", 10, 5d);
J. 反射泛型擦除----仅供理解反射用
a. 使用情况
例如:在泛型为String的集合里,添加Integer的数据
ArrayList list = new ArrayList();
list.add(100);
b. 能用泛型擦除的理论
伪泛型:在编译后的.class文件里面是没有泛型的。类型为Object。
可用反射的方法绕过编译,得到Class文件对象,直接调用add方法。
ArrayList<String> array = new ArrayList<String>();
array.add("a");
//反射方式,获取出集合ArrayList类的class文件对象
Class c = array.getClass();
//获取ArrayList.class文件中的方法add
Method method = c.getMethod("add",Object.class);
//使用invoke运行ArrayList方法add
method.invoke(array, 150);
method.invoke(array, 1500);
method.invoke(array, 15000);
System.out.println(array);
注:通过反射擦去泛型,将int类型的值插入String类型的集合后,由于类型无法确定,只能通过array.toString()查看里面的值,无法遍历。
K. 反射通过配置文件来决定运行的步骤
实现步骤:
- 准备配置文件,键值对
- IO流读取配置文件 Reader
- 文件中的键值对存储到集合中 Properties 集合保存的键值对,就是类名和方法名
- 反射获取指定类的class文件对象
- class文件对象,获取指定的方法
- 运行方法
/*
* 调用Person方法,调用Student方法,调用Worker方法
* 类不清楚,方法也不清楚
* 通过配置文件实现此功能
* 运行的类名和方法名字,以键值对的形式,写在文本中
* 运行哪个类,读取配置文件即可
*/
public class Test {
public static void main(String[] args) throws Exception{
//IO流读取配置文件
FileReader r = new FileReader("config.properties");
//创建集合对象
Properties pro = new Properties();
//调用集合方法load,传递流对象
pro.load(r);
r.close();
//通过键获取值
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//反射获取指定类的class文件对象
Class c = Class.forName(className);
Object obj = c.newInstance();
//获取指定的方法名
Method method = c.getMethod(methodName);
method.invoke(obj);
}
}
config.properties配置文件:
#className=cn.itcast.demo3.Student //“#”符号为注释
#methodName=study
className=cn.itcast.demo3.Person
methodName=eat
#className=cn.itcast.demo3.Worker
#methodName=job