1 类的加载过程
- 程序经过javac.exe命令编译后,产生一个或多个字节码文件(.class文件)
- 使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程称为类的加载。加载到内存中的类,称之为运行时类,此运行时类就作为Class的一个实例
- 加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取此运行时类
也就是说,Class的实例就对应着一个运行时类,一个类其实也是Class的一个对象,因此可以理解万事万物皆对象
2 获取Class实例的方式
/**
* 获取Class实例的方式
*
* @author Yorick
*
*/
public class ClassTest {
public static void main(String[] args) {
// 方式一:调用运行时类的.class属性
Class<Person> clazz = Person.class;
System.out.println(clazz);
// 方式二:调用运行时类的对象,调用.getClass()方法
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
// 方式三:调用Class的静态方法,调用forName()方法
try {
Class clazz3 = Class.forName("Reflection.Person");
System.out.println(clazz3);
} catch (Exception e) {
e.printStackTrace();
}
// 方式四:使用类的加载器:ClassLoader
try {
ClassLoader classLoader = ClassTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("Reflection.Person");
System.out.println(clazz4);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3 有Class对象的类型
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组(只要数组的元素类型和维度相同,就是同一个Class)
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
public static void test02() {
// 一般类
Class c1 = Object.class;
// 接口
Class c2 = Comparable.class;
// 一维数组
Class c3 = String[].class;
// 二维数组
Class c4 = int[][].class;
// 注解
Class c5 = Override.class;
// 基本数据类型
Class c6 = int.class;
// void
Class c7 = void.class;
// Class本身
Class c8 = Class.class;
System.out.println(c1 + "\n" + c2 + "\n" + c3 + "\n" + c4 + "\n" + c5 + "\n" + c6 + "\n" + c7);
}
4 类的加载过程
- 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化
![类的加载过程](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9zMS5heDF4LmNvbS8yMDIwLzA4LzEyL2FqeGd3OC5wbmc?x-oss-process=image/format,png)
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
- 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,例如:以Cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
- 初始化:
- 执行类构造器()方法的过程,类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的类构造器()方法在多线程环境中被正确的加锁和同步
5 ClassLoader
![类加载器](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9zMS5heDF4LmNvbS8yMDIwLzA4LzEyL2FqekhnQS5wbmc?x-oss-process=image/format,png)
- 类加载器的作用:用来把类(class)装载进内存
- 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构。然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些Class对象
- JVM规范定义了如下类型的类加载器:
![JVM规定的类加载器](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9zMS5heDF4LmNvbS8yMDIwLzA4LzEyL2FqenZFOC5wbmc?x-oss-process=image/format,png)
public static void test01() {
// 对于自定义类,使用系统类加载器进行加载
// 运行结果:sun.misc.Launcher$AppClassLoader@4e0e2f2a
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
// 调用系统类加载器的getParent():获取扩展类加载器
// 运行结果:sun.misc.Launcher$ExtClassLoader@2a139a55
ClassLoader classLoader2 = classLoader.getParent();
System.out.println(classLoader2);
// 调用扩展类加载器的getParent():无法获取引导类加载器
// 引导类加载器主要负责Java核心类库的加载,无法加载自定义类
// 运行结果:null
ClassLoader classLoader3 = classLoader2.getParent();
System.out.println(classLoader3);
// 以下获取结果为Null,说明String是通过引导类加载器加载的,不能被我们直接获取到
ClassLoader classLoader4 = String.class.getClassLoader();
System.out.println(classLoader4);
}
6 创建运行时类的对象及动态性演示
- 通过
newInstance()
方法创建实例 - 该方法内部调用了运行时类的空参构造器,因此要想使用该方法创建运行时类的对象,要求:
- 运行时类必须提供空参构造器
- 空参的构造器的访问权限得够用,通常设置为
public
- 在Javabean中,要求提供一个public的空参构造器原因:
- 便于通过反射,创建运行时类的对象
- 便于子类继承此运行时类时,默认调用
super()
时,保证父类有此构造器
package Reflection;
import java.util.Random;
/**
* 1. 通过反射获取类的实例
* 2. 模拟反射的动态性
* @author Yorick
*
*/
public class NewInstanceTest {
/*
* 1. 通过反射获取类的实例
*/
public static void test01() {
try {
Class<Person> clazz = Person.class;
// 获取person实例
Person person = clazz.newInstance();
System.out.println(person);
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 2. 模拟反射的动态性
*/
public static void test02() {
Random random = new Random();
int num = 0;
for (int i=0;i<10;i++) {
// 通过产生不同的随机数获取不同的类的实例
num = random.nextInt(3);
switch (num) {
case 0:
System.out.println(getInstance("Reflection.ClassLoaderTest"));
break;
case 1:
System.out.println(getInstance("Reflection.ClassTest"));
break;
case 2:
System.out.println(getInstance("Reflection.Person"));
break;
}
}
}
public static Object getInstance(String classPath) {
Object instance = null;
try {
Class clazz = Class.forName(classPath);
instance = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
public static void main(String[] args) {
test02();
}
}