先上代码抛出问题:
package com.myClassLoaderTest;
/**
* 用于对比的测试类
* 1.该类中有静态代码块。
* 2.测试Class.forName和classLoader的不同
*
* 测试结果
*
* @ Author: liu xuanjie
* @ Date: 2020/7/29
*/
public class TestObject
{
public static int value = 1;
public TestObject()
{
System.out.println("成功进入到TestClass的无参构造方法中!");
}
static
{
System.out.println("成功进入到TestClass的静态代码块中!");
}
public static void method()
{
System.out.println("成功进入到TestClass的静态方法中!");
}
}
上述Class为一个测试所用类,,,其中包含的组件有,静态成员变量,无参重写构造方法,静态代码块,静态方法。
package com.myClassLoaderTest;
/**
* 测试类
*
* @ Author: liu xuanjie
* @ Date: 2020/7/29
*/
public class TestClass
{
/**
* 主函数,程序入口
*/
public static void main(String[] args) throws Exception
{
String classNameHasPackage = "com.myClassLoaderTest.TestObject";
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
System.out.println("================下面是Clazz1的测试结果=====================");
Class clazz1 = Class.forName(classNameHasPackage);
System.out.println("================下面是Clazz2的测试结果==================================");
Class clazz2 = Class.forName(classNameHasPackage, false, classLoader);
System.out.println("================下面是Clazz3的测试结果=====================================");
Class clazz3 = classLoader.loadClass(classNameHasPackage);
}
}
这是一个测试类,,,主要测试Class.forName()和ClassLoader.loadClass()的区别。
看测试结果:
问题分析:
首先,说点预备知识,JVM把一个编译好的(.class)文件转换成内存中可使用创建的Class实例需要经过一系列过程。
分别为:::加载,验证,准备,解析,初始化。。。完成这五步之后,就可以实例化了。。。。
其中验证,准备,解析,又被统称为连接过程。
(有兴趣的同学自行参考相关知识,我的其它博客中也有简单介绍,但大体都是《深入理解JAVA虚拟机》一书中的叙述)
而我们要说的就是上述五个过程中的加载阶段。。。。
总结加载这一步的主要意义:::就是生成了一个类对应的java.lang.Class对象。。。
而我们的主角就是做的这件事情:
Class.forName()和ClassLoader.loadClass()都是根据一个类的全限定名,根据(.class)文件的字节码获取到对应的java.lang.Class对象。。。而他们之间的本质区别就是,,,结束的时期不一致。。。。
本质区别:
根据之前的测试用例,我们不难发现,
1. Class.forName()方法,最终执行到了静态代码块。。。。
2.Class.forName(name, false, loader)方法,没有执行到静态代码块。。。
3.ClassLoader.loadClass()方法,没有执行到静态代码块。。。。
所以到此,问题可以得到结论了:我们知道,在类的整个加载(不是上述五步中所说的加载,上述五步骤的总和)过程中,能够访问到静态代码块(注意这里说的不是静态方法,是静态代码块)的步骤就是“初始化”的这一步,这一步形成的<Clinit>()方法,自动收集静态代码块并执行,,,所以说Class.forName()这个方法下的过程已经完成了初始化。。。。
而相应的后续两种方法都是仅仅完成了第一步的加载过程,返回了一个类对应的Class实例,提供访问类信息的方法接口。
深入分析:
1.forName(name, boolean,Loader)方法
我们来看,这个方法区别于第一种方式的这个boolean是干嘛的,找到源码:
对比这两个方法,最终都是调用了底层的 forName0()方法,不同的就是这个boolean initialize布尔参数的不同。。
我们来看这个参数的官方解释:::
不出我们所料,,,加上这个boolean标识位控制的就是class是否被初始化。。。。
而测试用例中第一次的forName(name)方法,没有设置这个boolean值,但是看上述源码,其默认了true。。。
2.ClassLoader.loadClass(name)方法
首先该方法去调用了双参数的loadClass()方法,,又是多了一个boolean值,来看解释
意思是,,,如果该boolean标识变量为true的话,就会解析,当然了false就是不会。。。
而上述源码中,很明显,默认的是false。。。
并且还有一个小细节,双参数的loadClass()方法是 protected的,外部保护不可访问的。。。。