(一)虚拟机类加载机制
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被java虚拟机直接使用的java类型----Class类型,这就是虚拟机的类加载机制.
>通俗讲:【类加载就是把磁盘的字节码文件数据变为内存的Class对象】
1.2类加载过程
当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始化三步来实现对这个类的加载.
1)加载
就是指将class文件读入内存,并为之创建一个Class对象.
任何类被使用时系统都会建立一个Class对象
2)连接
验证是否有正确的内部结构,并和其他类协调一致
准备负责为类的静态成员分配内存,并设置默认初始化值
解析将类的二进制数据中的符号引用替换为直接引用
3)初始化
1.3类的加载时机【类被使用的才会被加载】
1.创建类的实例
2.类的静态成员使用
3.类的静态方法调用
4.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
5.初始化某个类的子类
6.直接使用java.exe命令来运行某个主类
(二)类加载器
类加载器是负责加载Class类【字节码文件数据】的对象,将class文件(硬盘)数据加载到内存中,并为之生成对应的java.lang.Class对象.
2.2类加载器的分类
>1.Bootstrap ClassLoader 引导类加载器
也被称为根类加载器,负责Java核心类的加载,比如System,String等.
>2.Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录.
>3.Application ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径.
>4.自定义类加载器
开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求.
2.3双亲委派机制
> 1.双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器.每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载.
> 2.双亲委派模型工作过程:
> 1)当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成.
> 2)当Extension ClassLoader收到一个类加载请求时,他也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成.
> 3)如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载.
> 4)如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载.
> 5)如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载.
> 6)如果均加载失败,就会抛出ClassNotFoundException异常.
3.比如:
加载一个自定义的类Person,触发了系统类加载器,不加载委托给父类【扩展类加载器去加载】,父类接收到请求后又委托给父类的的父类【根类加载器】,根加载器尝试加载这个类,由于Person自定义的类不在jdk的核心类库中,所以加载失败,请求重新回到扩展类加载器,尝试加载这个类,由于Person类是自定义的在jre的lib下面找不到对应的类,也加载失败,请求重新回到系统类加载器,尝试加载,在第三方的相关资源处【自己书写代码的包中,导入的jar包中等查找对应的类】找到了加载成功,没有找到报错。
#### 2.4ClassLoader类
1.ClassLoader 叫做类加载器.虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流” 也就是.class字节码文件,这个动作放到java虚拟机外部去实现,以便让应用程序自己决定去如何获取所需要的类,实现这个动作的代模块称之为“类加载器”.
2.ClassLoader的方法:
static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器
ClassLoader getParent():返回父类加载器进行委派
`ClassLoader.getResourceAsStream():
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//Application ClassLoader【系统类加载器】
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//Extension ClassLoader【扩展类加载器)
//注意扩展类加载器的父类为引导类加载器【Bootstrap ClassLoader】,由于它是使用C++写的,所以我们java访问不了
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);//所以此处为null
(三)反射应用
3.1反射机制
> 反射是指在代码运行时去获取一个类对象的变量和方法信息.然后通过获取到的信息来创建对象,调用方法的一种机制.
> 由于这种动态性可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展.
3.2获取Class类的对象
>1.Class类: Class类型的实例表示正在运行的java应用程序的类或者接口.
>2.Class类的对象: 想获取和操作类中的内容,首先要获取类的字节码对象(Class类对象),每一个正在运行的类,都有对应的字节码对象,获取了类的字节码对象,就可以使用这个对象的所有方法,这些方法都定义在Class类型中.
>3.三种获取Class类对象的方式:【使用反射技术操作Person类中的相关资源为例】
1)类名.class属性
//类名.类名属性
Class<Person> personClass = Person.class;
2)对象名.getClass()方法
//类对象.getClass
Person person = new Person();
Class<? extends Person> aClass = person.getClass();
3)Class.forName(全类名)方法
//Class.forName(权限定类名)
Class<?> aClass1 = Class.forName("com.tlc.reflect.Person");
3.3反射获取构造方法并使用
>1.Class类获取构造方法对象:
Constructor<?>[] getConstructors():返回所有公共构造方法对象的数组
Class<?> aClass = Class.forName("com.tlc.reflect.Person");
Constructor<?>[] con = aClass.getConstructors();
Constructor<?>[] getDeclaredConstructors():返回所有构造方法对象的数组
Constructor<?>[] dec = aClass.getDeclaredConstructors();
Constructor getConstructor(Class<?>... parameterTypes):返回单个公共构造方法对象
Constructor<?> constructor = aClass.getConstructor(String.class, int.class);
Constructor getDeclaredConstructor(Class<?>...parameterTypes):返回单个构造方法对象
Constructor<?> dConstructor = aClass.getDeclaredConstructor(String.class, int.class);
2.Constructor类型:
1)表示构造方法类型,这个类的每个对象,都是一个确定的,具体的构造方法
2)构造方法对象应该具有的功能: 获取构造方法各种信息(构造方法修饰符、构造方法名称、构造方法的参数列表、构造方法的注解),最基本的一个功能就是,创建对象.
3.Constructor类用于创建对象的方法:
T newInstance(Object...initargs) :根据指定的构造方法创建对象,参数为构造方法实际参数
//有参构造创建对象
Object o = dConstructor.newInstance("阿弥陀佛", 998);
//提升作用域
Person person = null;
//向下转型
if(o instanceof Person) {
person = (Person)o;
}
System.out.println(person);
3.4反射获取成员变量并使用
>1.Class类获取成员变量对象:
Field[] getFields():返回所有公共成员变量对象的数组
Field[] fields = aClass.getFields();
Field[] getDeclaredFields():返回所有成员变量对象的数组
Field[] declaredFields = aClass.getDeclaredFields();
Field getField(String name):返回单个公共成员变量对象
//获取单个public修饰的成员变量
Field age1 = aClass.getField("age");
Field getDeclaredField(String name):返回单个成员变量对象
//获取单个【任意权限】的成员变量
Field age = aClass.getDeclaredField("name");
2.Field类型: 表示一个成员变量类型,每个对象都是一个具体的成员变量
作用: 获取成员变量的各种信息(修饰符、注解、名称),做各种数据类型的转换.
3.Field类用于给成员变量赋值的方法:
set(Object obj, Object value): 用于给obj对象的,该成员变量,赋value值
4.Field类获取成员变量值的方法:
get(Object obj): 用于获取obj对象的指定成员变量值
Field age = aClass.getDeclaredField("name");
//操作属性的值【属性对象自己操作内部的属性】
// 得到一个Person对象:
// 方式一:new Person()
// 方式二:采用反射技术操作构造对象创建对应字节码文件类的对象
// 方式三:直接使用Class对象获取
Object o = aClass.newInstance();
age.set(o, 13);
//由于属性被封装,直接操作不了,所以就需要后面的暴力反射了
System.out.println(age.get(o));
3.5获取类中的成员方法并执行
>1.Class类获取成员方法对象:
Method[] getMethods():返回所有公共成员变量对象的数组
Method[] methods = aClass.getMethods();
Method[] getDeclaredMethods():返回所有成员变量对象的数组
Method[] methods1 = aClass.getDeclaredMethods();
Method getMethod(Class<?>... parameterTypes):返回单个公共成员变量对象
Method method = aClass.getMethod("方法名", String.class);
Method getDeclaredMethod(Class<?>... parameterTypes):返回单个成员变量对象
Method method1 = aClass.getDeclaredMethod("方法名", int.class);
2.Method类型: 表示一个成员方法类型,每个对象都是一个具体的成员方法
作用: 获取成员方法的对象,调用执行该方法.
3.Method类用于给成员方法执行的方法:
invork(Object o ,实参);
Object o = aClass.newInstance();
//获得Method对象
Method method = aClass.getMethod("show", String.class);
//调用方法
method.invoke(o, "苗苗");
获取对象的几种方式【方法和属性只有对象才有权调用】:
1.直接new对象;
2.使用反射对象的构造创建对象;
3.反射对象直接newInstance( );
应用代码:
package com.tlc.reflect;
/**
* 定义一个配置文件run.properties里面有全限定类名和方法名:
*定义一个类, 类中包含一个方法: public int getSum(int x, int y), 做两数求和
* 读取配置文件内容,使用反射运行Person类中的对应方法getSum
*/
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class Goddess {
public static void main(String[] args) {
//1.正向调用getSum方法(1·获取方法所在类对象;1.对象调用方法)
Demo demo = new Demo();
int sum = demo.getSum(12, 12);
System.out.println(sum);
//2.利用反射调用方法【1.获取方法所在类的反射对象;2.反射对象获取方法对象】
try {
//创建一个属性集对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("Item/mm/resource/run.properties"));
//获取配置文件中的权限定类名和方法名
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//这里可以先查看是否获取
System.out.println(className);
System.out.println(methodName);
Class<?> aClass = Class.forName(className);
Method method = aClass.getMethod(methodName, int.class, int.class);
//反射对象实例化对象
Object o = aClass.newInstance();
//方法对象调用方法
Object invoke = method.invoke(o, 12, 12);
System.out.println(invoke);
} catch (ClassNotFoundException | NoSuchMethodException | FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
getName方法所在类:
public class Demo {
public int getSum(int x, int y) {
return x + y;
}
}
配置文件【run.properties】:
【利用反射完成泛型檫除】
ArrayList<String> list = new ArrayList<>();
//获取Class对象
Class<? extends ArrayList> listClass = list.getClass();
//获取add方法对象
Method add = listClass.getMethod("add", Object.class);
add.invoke(list, 1314);
System.out.println(list);
3.6暴力反射
1.通过Class类中:
> getDeclaredXXX方法: 可以获取类中的所有声明的成员(属性、方法、构造),私有的成员也可以获取到.但是私有成员进行访问使用时,会因为权限问题导致失败,因此就需要暴力反射解决访问私有的问题。
2.修改该对象的访问权限:
> AccessibleObject类是Field,Method和Constructor对象的基类. 它提供了将反射对象标记为在使用它时抑制默认Java语言访问控制检查的功能.
> setAccessible(boolean flag): true的值表示反射对象应该在使用时抑制Java语言访问检查,false的值表示反映的对象应该强制执行Java语言访问检查.
3.一旦设定当前对象可以访问,私有的成员也可以被访问,被修改.