Class.forName和ClassLoader.loadClass的区别

一、前言

在Java中,类加载器把一个类装入Java虚拟机中,要经过三步来完成:加载、连接和初始化,其中连接又分为验证、准备和解析三个阶段。加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段可以在初始化阶段之后发生,也称为动态绑定或晚期绑定。各个步骤的主要工作如下:

  • 加载:查找和导入类或接口的二进制数据;
  • 连接:又可以分成校验、准备和解析三步,其中解析步骤是可以选择的;
    • 验证:检查导入类或接口的二进制数据的正确性;
    • 准备:给类的静态变量分配并初始化存储空间;
    • 解析:将符号引用转成直接引用;
  • 初始化:激活类的静态变量的初始化Java代码和静态Java代码块。

而类加载的方式也有以下几种方式:

  • 通过Class.forName()方法动态加载
  • 通过ClassLoader.loadClass()方法

那么通过Class.forName()加载和通过ClassLoader.loadClass加载有啥区别呢?

二、分析

1、首先看下Class.forName()源码:
@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

可以看到底层调用的forName0(className, true, ClassLoader.getClassLoader(caller), caller),第二个参数initialize表示是否执行类中的静态代码块、对类中的静态变量赋值等初始化操作,默认给的true。

2、在Class类中还有另一个方法,可以自己传入initialize的值,源码如下:
/**
* @param name       fully qualified name of the desired class
* @param initialize if {@code true} the class will be initialized.
*                   See Section 12.4 of The Java Language Specification.
* @param loader     class loader from which the class must be loaded
* @return           class object representing the desired class
*
* @exception LinkageError if the linkage fails
* @exception ExceptionInInitializerError if the initialization provoked
*            by this method fails
* @exception ClassNotFoundException if the class cannot be located by
*            the specified class loader
*
* @see       java.lang.Class#forName(String)
* @see       java.lang.ClassLoader
* @since     1.2
*/
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException
{
    Class<?> caller = null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        // Reflective call to get caller class is only needed if a security manager
        // is present.  Avoid the overhead of making this call otherwise.
        caller = Reflection.getCallerClass();
        if (sun.misc.VM.isSystemDomainLoader(loader)) {
            ClassLoader ccl = ClassLoader.getClassLoader(caller);
            if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader, caller);
}

官方给的参数注释写的也很清楚:@param initialize if {@code true} the class will be initialized. 如果给的true,类就会被初始化。


3、再看下ClassLoader.loadClass的源码:
public Class<?> loadClass(String name) throws ClassNotFoundException {
	return loadClass(name, false);
}

###############萌萌哒分割线#########################

/**
 * @param  name
 *         The binary name of the class
 *
 * @param  resolve
 *         If true then resolve the class
 *
 * @return  The resulting Class object
 *
 * @throws  ClassNotFoundException
 *          If the class could not be found
 */
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            // 重点:这里根据参数resolve的值控制是否调用连接方法
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }    

	###############萌萌哒分割线#########################
    /**
     * Links the specified class.  This (misleadingly named) method may be
     * used by a class loader to link a class.  If the class c has
     * already been linked, then this method simply returns. Otherwise, the
     * class is linked as described in the "Execution" chapter of
     * The Java™ Language Specification.
     *
     * @param  c
     *         The class to link
     *
     * @throws  NullPointerException
     *          If c is null.
     *
     * @see  #defineClass(String, byte[], int, int)
     */
    protected final void resolveClass(Class<?> c) {
        resolveClass0(c);
    }

根据注释resolveClass方法是类加载过程中的连接操作,loadClass方法的注释可以看出参数resolve是控制是否执行连接,根据前言部分类加载的几个步骤阶段顺序,没有连接也就不会有后面的初始化,而ClassLoader.loadClass()默认resolve传的false,所以也就不会执行初始化。


4、测试验证
//测试类
public class LycClassLoadDemo {
    static {
        System.out.println(".....静态代码块.....");
    }

    private static String str = "ddd";

    private static void testMethod() {
        System.out.println("....静态方法....");
        str = "testMethod";
    }
}
public class TestClassForName {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("com.lyc.learing.LycClassLoadDemo");
        System.out.println("...test main...");
    }
}

上面Class.forName方式执行结果:
…静态代码块…
…test main…


public class TestClassLoader {

    public static void main(String[] args) throws ClassNotFoundException {

        ClassLoader.getSystemClassLoader().loadClass("com.lyc.learing.LycClassLoadDemo");
        System.out.println("...test main...");
        
    }
}

上面ClassLoader.loadClass方式执行结果:
…test main…


三、结论

通过源码及测试结果可以知道:

Class.forName():把类的.class文件加载到JVM中,但是在对类进行加载的同时会执行类中的static静态代码块
ClassLoader.loadClass():把.class文件加载到JVM中,不会执行static代码块中的内容,只有在newInstance才会去执行


四、题外话

1、对于Class.forName的使用最熟悉的应该就是加载数据库驱动,下面是Mysql驱动的部分源码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

可以看到mysql驱动注册到DriverManager的操作代码是写在静态代码块中的,而我们也是使用Class.forName方式加载驱动的。

参考文章:
[1] https://zhuanlan.zhihu.com/p/43845064

你可能感兴趣的:(面试问题汇总,java,面试,类加载)