JavaSE — 类加载机制和反射
一、类的加载、连接和初始化

1、JVM和类
当程序启动时,会先启动一个Java虚拟机进程,程序的运行都是在Java虚拟机进程里的
-
JVM进程被终止的情况:
1)、程序运行到最后正常结束
2)、程序运行到使用
System.exit()
或者Runtime.getRuntime().exit()
代码处结束3)、程序运行遇到了未捕获的异常或者报错了
4)、程序所在平台强制结束了JVM进程
注意:其中System
和Runtime
分别为调程序运行平台方法和运行环境方法
2、类的加载
当程序使用某个类时,如果类还未被加载到内存中,系统就会对类进行加载、连接、初始化三个步骤
注意:类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。
- 其中,类加载是指将类的
class
文件读入内存,并为之创建一个java.lang.Class
对象
3、类的连接
当类被加载之后,系统为之生成了一个Class对象,接着就是连接阶段,负责将类的二进制文件合并到JRE中,分为3个步骤:
- 验证:用于检验被加载的类是否具有正确的内部结构,并和其他类协调一致
- 准备:负责为类变量分配内存,并设置初始值
- 解析:将类的二进制数据中对常量池的符号引用替换成直接引用
4、类的初始化
-
初始化阶段,就是虚拟机对类进行变量的初始化
-
对类变量指定初始值有两种方式:
1)、声明变量时指定初始值
2)、使用静态初始化块为类变量指定初始值
-
注意:JVM最先初始化的总是
java.lang.Object
类(因为如果类有父类,会先初始化父类,而Object类是所有类的父类)
5、类初始化的时机
程序首次通过以下6种方式使用类或接口时,系统会初始化该类或者接口
-
创建类的实例(包括
new
一个对象,通过反射创建,通过反序列化创建) -
访问某个类或者接口的静态变量(注意:当该静态变量在编译时就确定了,实际上后面的访问,并没有,而是直接使用常量)
-
调用某个类的静态方法
-
使用反射强制创建某个类或者接口对应的
java.lang.Class
对象,例如Class.forName("Car");
如果系统还没初始化
Car
类,这个时候也会被初始化,并返回Car
类对应的java.lang.Class
对象 -
初始化某个类的子类(初始化子类时,会先初始化父类)
-
直接使用
java.exe
命令运行某个主类,运行时会先初始化该主类
二、类加载器
类加载器负责将
.class
文件(可能在磁盘或者网络上)加载到内存中,并生成相应的java.lang.Class
对象
1、类加载机制

JVM的类加载机制主要是以下3种
- 全盘负责:当一个类加载器加载某个
Class
时,该Class
所依赖的和引用的其他Class
也由该加载器加载(好人帮到底,送佛送到西) - 父类委托:尝试先让父类加载器加载,没有再依次返回来加载
- 缓存机制:会保证所有加载过的
Class
都会被缓存,当程序中需要使用某个Class
时,类加载器会先从缓存区搜索该Class
,缓存区没有,才会读取对应的二进制数据,加载(这也是为什么代码改了,需要重新启动JVM才能生效)
双亲委派机制:试图先让父类加载器加载,一直向上直到根类加载器,如果没有,在向下依次看有无,有则加载
好处有两点:
1、避免重复加载,父类加载器加载了,子类就不用加载了
2、安全,防止Java核心API被篡改(因为优先有父类加载器加载,自定义加载器最后才有机会执行)
注意:区分加载过没,是由
类的全限定名
+类加载器
作为唯一标识
三、反射
程序在运行时获得对象和类的真实信息
1、获得Class对象
类被加载后,系统会为每个类生成一个对应的
Class
对象,通过该对象,就可以访问到JVM中的这个类
在Java程序中,获得Class对象有3中方式(推荐第一种)
- 调用某个
类的class
属性:例如Car.class
获得Car
类对应的Class
对象 - 使用
Class类
的的forName(String s)
静态方法:需要传入类的全限定类名(完整包名) - 调用对象的
getClass()
方法
通过调用类的class属性
获得Class对象
的好处
- 代码安全,在编译阶段就可以确定需要的Class对象是否存在
- 程序性能更好,无需调用方法
2、Class对象的使用
通过过
Class对象
可以得到大量的Method
方法、Constructor
构造器、Field
属性等对象,这些对象分别代表该类所包括的方法、构造器和属性等,程序还可以通过这些对象来执行实际的功能,例如调用方法、创建实例
// 根据类名获取对应的Class对象
Class<?> cla = Car.class;
// 可以通过Class对象获得类的的各种信息
// 获得Car类的所有public方法
Method[] methods = testClass.getMethods();
for(Method m:methods){
System.out.println(m.toString());
}
3、 使用反射获得操作对象
程序可以通过Method 对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的属性值
- 通过Class对象获取指定的
Constructor
(构造器)对象 - 调用
Constructor
对象的newInstance()
方法创建该 Class 对象对应类的实例
// 根据类名获取对应的Class对象
Class<?> cla = Car.class;
// 使用Class对象对应类的空参构造器(公共的)
Constructor car = cla.getConstructor()
// 根据参数列表,获取指定构造函数(公共的)
Constructor car2 = cla.getConstructor(String.class, int.class);
// 通过构造函数的newInstance()实例化对象
Object c = car2.newInstance();
- 直接通过Class对象的
newInstance()
方法直接获得(实际也是调了默认的空构造器)
// 创建被反射类的对象实例的快捷方式 (常用)。
// 前提:必须要有公有的空参构造器 (如果是默认权限,反射的类必须在本包中)
Object c2 = cla.newInstance();
5、操作数组
在
java.lang.reflect
包下还提供了一个Array
类,Array对象可以代表所有的数组。程序可以通过使用Array来动态地创建数组,操作数组元素等。
// 创建一个元素类型为String,长度为10的数组
Object arr = Array.newInstance(String.class, 5);
// 赋值
Array.set(arr, 3, "这是下标为3的元素的值");
// 取值, 值为 "这是下标为3的元素的值"
Object arrValue = Array.get(arr, 3);
6、使用反射生成JDK动态代理
在Java的
java.lang.reflect
包下提供了一个Proxy
类和一个InvocationHandler
接口,可以生成JDK动态代理类或者动态代理对象注:代理模式,特征是代理类与委托类有同样的接口,代理对象不真正实现服务
// 创建一个InvocationHandler对象
// 其中,MyInvocationHandler 实现 InvocationHandler 接口
InvocationHandler handler = new MyInvocationHandler(...);
// 使用 Proxy 直接生成一个动态代理对象
Car c = (Car)Proxy.newProxyInstance(Car.class.getClassLoater(),
new Class[]{Car.class},
handler);
未完待补充!
7、泛型和反射
在反射中,可以使用泛型,避免使用反射生成的对象需要强转
当强转可能导致出错:
public class MyFactory{
// 代码省略了异常处理
public static Object getInstance(String className){
// 创建指定类对应的Class对象
Class cla = ClassName.class;
// 返回使用该Class对象直接创建实例(其实也是调了构造器)
return cla.newInstance();
}
}
// 获取实例后需要强制类型转换,
Car c = (Car)MyFactory.getInstance("Car");
使用泛型避免强转:
public class MyFactory{
// 返回一个 T 对象,参数为泛型化的Class对象
public static <T> T getInstance(Class<T> cls){
// 返回使用该Class对象直接创建实例(其实也是调了构造器)
return cls.newInstance();
}
}
// 获取实例后不用强制类型转换
Car c = MyFactory.getInstance(Car.class);