首先,先要了解整个JVM加载类的流程,可以查看我的另一篇博客:点击打开链接
双亲委派模型:
当一个类加载器接收到类加载请求时,会先将请求传给父加载器,父加载器又会传给自己的父加载器,最终传给启动类加载器,当父加载器无法加载类时(在自己的搜索路径中找不到),子类加载器才会开始加载类。
forName方法
forName函数有两种形式:
public static Class<?> forName(String className) throws ClassNotFoundException
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException //允许指定是否初始化,并且指定类加载器 ,若ClassLoader为null,则表示使用启动类加载器
若采用第一种形式,则默认为执行初始化,类加载器应该是从System Class Loader(JVM实现者提供的用户自定义类加载器,在编程者不指定类加载器情况下默认装载用户类,可以通过ClassLoader.getSystemClassLoader()获取)开始,满足双亲委派模型的工作形式,我们来看看下面的代码:
package pkgtry;
class A
{
static
{
System.out.println("加载类A");
}
}
public class Try {
public static void main(String[] args) {
try
{
Class.forName("pkgtry.A");//参数为类的全限定名,即包名+类名
}
catch(ClassNotFoundException e)
{
System.out.println("未找到类");
}
}
}
执行结果为:
若我们采用第二种形式,并且将第二个参数设为false,则不会进行初始化,但其他类加载操作还是会进行,来看看下面的代码:
package pkgtry;
class A
{
static
{
System.out.println("加载类A");
}
}
public class Try {
public static void main(String[] args) {
try
{
Class.forName("pkgtry.A",false,ClassLoader.getSystemClassLoader());
}
catch(ClassNotFoundException e)
{
System.out.println("未找到类");
}
}
}
输出结果为:
没有抛出异常,说明加载成功。
若将第二个参数改为true,输出结果为:
loadClass方法
loadClass函数有两种形式:
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException //第二个参数表示是否在转载完后进行验证、准备、解析。
public Class<?> loadClass(String name) throws ClassNotFoundException
loadClass不是静态方法,而ClassLoader是虚基类,所以只能继承ClassLoader后进行调用,先来调用第二种形式:
package pkgtry;
class A
{
static
{
System.out.println("加载类A");
}
}
public class Try {
public static void main(String[] args) {
try
{
ClassLoader.getSystemClassLoader().loadClass("pkgtry.A");//相当于ClassLoader.getSystemClassLoader().loadClass("pkgtry.A",false);getSystemClassLoader返回值类型继承自ClassLoader
}
catch(ClassNotFoundException e)
{
System.out.println("未找到类");
}
}
}
结果为:
什么都没有,这是因为loadClass默认情况只是加载类,不会进行加载类后的一些列动作。
接下来调用第一种方式,并将第二个参数指定为true,结果还是一样没有输出,这是因为loadClass不会执行初始化操作,第二个
参数指定为true只会执行类加载后的验证、准备、解析步骤。
两者的使用场景
以下为个人观点
两个函数均可指定类加载器,forName的第二种形式可以指定类加载器,loadClass通过调用者指定类加载器,loadClass可以指定只加载类,类的验证、准备、初始化在第一次使用该类时进行,有些类完成整个装载或初始化的时间比较长,若不急着用该类,可以使用该方法节省时间,forName相较于loadClass,除了加载类,一定会进行验证、准备、解析,我们可以指定是否初始化,当我们急着使用类时,就使用该方法。
接下来给出个双亲委派模型工作的例子:
ClassLoader.getSystemClassLoader()获取的类加载器可以加载用户自定义的类,若我们获取该类加载器后加载java.lang.String,看会不会出现异常,若没有出现异常,说明String类被父类加载器加载,即满足双亲委派的工作方式,否则,会出现异常。
public class Try {
public static void main(String[] args) {
try
{
ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
}
catch(ClassNotFoundException e)
{
System.out.println("未找到类");
}
}
}
运行结果如下:
无异常。