loadClass()和forName()的区别

类加载的方式

主要有两种:

  • 隐式加载:使用 new + 构造方法时,隐式的调用类加载器,加载对应的类到 JVM 中,是最常见的类加载方式。
  • 显式加载:使用 loadClass()、forName() 等方法显式的加载需要的类,对于显式加载这种类加载方式来讲,当我们获取到了 Class 对象后,需要调用 Class 对象的 newInstance() 方法来生成对象的实例。

两种类加载方式的区别:

  • 隐式加载能够直接获取对象的实例,而显式加载需要调用 Class 对象的 newInstance() 方法来生成对象的实例。
  • 隐式加载能够使用有参的构造函数,而使用 Class 对象的 newInstance() 不支持传入参数,如果想使用有参的构造函数,必须通过反射的方式,来获取到该类的有参构造方法。

关于 Java 虚拟机类加载机制更多的内容请参考我另一篇文章:虚拟机类加载机制总结
 

loadClass() 和 forName() 的区别

首先,不管是使用 loadClass() 还是 forName(),对于任意一个类,我们都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这也是实现反射的重要方法。

那么这两个方法的区别在哪呢?我们通过源码来分析。

1. loadClass()

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}

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();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
}

protected final void resolveClass(Class<?> c) {
        resolveClass0(c);
}

private native void resolveClass0(Class<?> c);

我们主要来看一下 resolve 这个布尔变量,resolve 即解析,我们可以看到,在重载的 loadClass() 方法中,如果 resolve 为 true 的话,会执行 resolveClass() 这个方法。我们再来看一下 resolveClass() 这个方法中的这一句注释。
在这里插入图片描述
我圈红框的这里,Links the specified class :链接指定的类。

我们再回过头来看看,return loadClass(name, false);,默认的 loadClass() 方法会传入 false,也就是说,使用默认的 loadClass() 方法获得的 Class 对象是还没有执行链接的。那如果你了解 Java 虚拟机的类加载机制的话,这个时候你便应该知道,使用 loadClass() 方法获得的 Class 对象只完成了类加载过程中的第一步:加载,后续的操作均未进行。

2. forName()

@CallerSensitive
public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

@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);
}

我们也是来重点关注一个布尔变量,那就是 initialize 变量。initialize 即初始化。

return forName0(className, true, ClassLoader.getClassLoader(caller), caller);

我们可以看到,默认传入的 initialize 为 true.说明使用 Class.forName() 方法获得 Class 对象是已经执行完初始化的了(注意这里指的是类加载过程中的最后一步:初始化,而非是实例化对象操作的初始化

所以,看到这里相信大家也明白了,这两个方法的区别:

  • 使用 loadClass() 方法获得的 Class 对象只完成了类加载过程中的第一步:加载,后续的操作均未进行。
  • 使用 Class.forName() 方法获得 Class 对象是已经执行完初始化的了

两者的使用场景

对于 Class.forName() 方法,相信大家并不陌生,因为在 Web 开发中,经常会用到使用这个方法来加载 MySQL 的驱动,Class.forName("com.mysql.jdbc.Driver");

我们看一下驱动的源码会发现在类 Driver 中有这样一个静态代码块,静态代码块会在类加载过程中的初始化阶段执行。

所以我们现在应该知道了什么时候使用 Class.forName() 方法:在需要对类进行初始化的时候。

 static {  
          try {
                //往DriverManager中注册自身驱动
                java.sql.DriverManager.registerDriver(new Driver());  
           } catch (SQLException E) {  
                throw new RuntimeException("Can't register driver!");  
           }
}

那么为什么还要有 loadClass() 呢?

举个小例子,在 Spring IOC 中,在资源加载器获取要读入的字节的时候,即读取一些 Bean 的配置的时候,如果是以 classpath 的方式来加载,就需要使用 ClassLoader 的 loadClass() 方法来加载。之所以这样做,是和 Spring IOC 的 Lazy Loading 有关,即延迟加载。Spring IOC 为了加快初始化的速度,大量的使用了延迟加载技术,而使用 ClassLoader 的 loadClass() 方法不需要执行类加载过程中的链接和初始化的步骤,这样做能有效的加快加载速度,把类的初始化工作留到实际使用到这个类的时候才去执行。

猜你喜欢

转载自blog.csdn.net/u013568373/article/details/94040135