要想搞清楚这两个方法的区别,我们需要了解一下Class的加载过程。Class的加载过程分为三步:
- loading(装载)
- linking(链接)
- initializing(初始化)
大家可以通过这篇文章:Java魔法堂:类加载机制入了个门来了解类的详细加载过程。阅读以上文章后,我们一起分析一下两个方法的区别,如有不正之处,欢迎批评指正。
1、forName()
Class.forName()有两个重载的方法,都是public
方法。
public static Class<?> forName(String className)
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)
public static Class
@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
public static Class
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
caller = Reflection.getCallerClass();
if (loader == null) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (ccl != null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
通过对比我们发现两个方法最终都调用了私有的forName0'方法,而'Class.forName(String className)
方法中initialize
参数默认为true
,Class.forName(String name, boolean initialize, ClassLoader loader)
方法的initialize
参数由用户来指定。javadoc中关于该方法的initialized
参数的说明如下:
@param initialize whether the class must be initialized
参数initialize 表示该类是否必须被初始化
通过initialize
参数我们可以发现,该参数控制了类加载过程的第三步(初始化),该参数在’Class.forName(String className)方法中默认值为
true`,因此在类加载的过程中会初始化类的相关信息,比如类中的静态块会被执行。因此我们得出结论:
Class.forName(className)
等同于
initialize = true;
Class.forName(className, initialize, loader)
示例代码:
ClassLoaderDemo1代码:
package com.ips.classloader;
public class ClassLoaderDemo1 {
public static void main(String [] args){
try {
ClassLoader system = ClassLoader.getSystemClassLoader();
Class<Config> cls = null;
System.out.println("----------方法1----------");
cls = (Class<Config>)Class.forName("com.ips.classloader.Config");
System.out.println("----------方法2----------");
cls = (Class<Config>)Class.forName("com.ips.classloader.Config", false, system);
System.out.println("----------方法3----------");
cls = (Class<Config>)Class.forName("com.ips.classloader.Config", true, system);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
Config代码:
package com.ips.classloader;
public class Config {
private String name;
private static boolean flag;
static {
flag = false;
System.out.println("flag 的值为:" + flag);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
我们通过三种方式加载Config类,在Config类中有一段静态代码块,通过println
函数来判定静态代码块是否被执行。
执行结果如下:
--------------------
--------------------
--------------------
我们发现方法3没有输出flag的值,这是为什么呢?原因是类加载过程中的缓存机制,由于方法1已经加载了该类,因此方法3不会再次加载该类,所以没有输出flag值,为了测试缓存的问题,我们将方法1与方法3的位置互换,程序的执行结果如下,可以看到方法3加载了该类,并且输出去了flag值,而方法1没有输出flag值。我们每次修改完代码都需要重启JVM来执行新的代码也是由类加载的缓存机制造成的。
--------------------
--------------------
--------------------
2、loadClass()
ClassLoader.getSystemClassLoader().loadClass()有两个重载方法,一个public
方法,一个protected
方法。
public Class<?> loadClass(String name)
protected Class<?> loadClass(String name, boolean resolve)
public Class
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
通过对比我们发现方法1调用了方法2,并且调用方法2的过程中,resolve
参数的值为false。javadoc中关于该方法的resolve
参数的说明如下:
@param resolve If <tt>true</tt> then resolve the class
参数resolve 如果值为true则resolve这个类
我们看一下resolve
为true
时,方法的执行逻辑
if (resolve) {
resolveClass(c);
}
在看一下resolveClass
方法
/**
* Links the specified class. This (misleadingly named) method may be
* used by a class loader to link a class. If the class <tt>c</tt> has
* already been linked, then this method simply returns. Otherwise, the
* class is linked as described in the "Execution" chapter of
* <cite>The Java™ Language Specification</cite>.
* </p>
*
* @param c
* The class to link
*
* @throws NullPointerException
* If <tt>c</tt> is <tt>null</tt>.
*
* @see
*/
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
从javadoc中我们可以看出,resolveClass
方法主要是用来链接指定的类,通过resolve
参数我们可以发现,该参数控制了类加载过程的第二步(链接),该参数值为false
时不进行类的链接,为true
时进行类的链接,由于loadClass(String name, boolean resolve)为protected
方法,因此我们无法通过ClassLoader直接调用。
示例代码:
package com.ips.classloader;
public class ClassLoaderDemo1 {
public static void main(String [] args){
try {
ClassLoader system = ClassLoader.getSystemClassLoader();
Class<Config> cls = null;
System.out.println("-----方法4-----");
cls = (Class<Config>)ClassLoader.getSystemClassLoader().loadClass("com.ips.classloader.Config");
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
执行结果:
----------
没有执行Config
类的静态代码块,由此可见Config只是进行了装载,没有进行链接与初始化。
3、关于mysql jdbc
我们在进行数据库操作的时候,通常采用如下的方式加载数据库驱动。
Class.forName("com.mysql.jdbc.Driver");
为什么不是ClassLoader.getSystemClassLoader().loadClass()呢?这是因为Driver
类中的静态代码块需要进行一些初始化配置。代码如下:
Copyright 2002-2004 MySQL AB, 2008 Sun Microsystems
package com.mysql.jdbc;
import java.sql.SQLException;
/**
* The Java SQL framework allows for multiple database drivers. Each driver
* should supply a class that implements the Driver interface
*
* <p>
* The DriverManager will try to load as many drivers as it can find and then
* for any given connection request, it will ask each driver in turn to try to
* connect to the target URL.
*
* <p>
* It is strongly recommended that each Driver class should be small and
* standalone so that the Driver class can be loaded and queried without
* bringing in vast quantities of supporting code.
*
* <p>
* When a Driver class is loaded, it should create an instance of itself and
* register it with the DriverManager. This means that a user can load and
* register a driver by doing Class.forName("foo.bah.Driver")
*
* @see org.gjt.mm.mysql.Connection
* @see java.sql.Driver
* @author Mark Matthews
* @version $Id$
*/
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
4、总结
- Class.forName() 方法中,
initialize
参数控制类在加载的过程中是否进行初始化。 - ClassLoader.getSystemClassLoader().loadClass()方法中,
resolve
参数控制类在加载的过程中是否进行链接。