一、Java反射中获取Class对象的三种方式
在Java反射中,反射的入口就是class,获取class的方式有三种
- Class.class;
- Class.forName();
- 对象.getClass()
对三者的简单理解
- Class.class的形式会使JVM使用类加载器将类装入内存(前提是类还没有装入内存),不做类的初始化工作,返回Class对象。
- Class.forName()的形式会装入类并做类的静态初始化,返回Class对象。
- 对象.getClass的形式会对类进行静态初始化、非静态初始化,返回引用运行时真正所指的对象(因为子对象的引用可能会赋给父对象的引用变量中)所属的类的Class对象。
静态初始化是指在加载类的时候初始化,而非静态初始化是new对象的时候初始化。
三种情况在生成Class对象的时候都会判断内存中是否已经加载此类。
注:只有使用Class.forName()时才会进行异常处理,因为Class.forName()要加载类路径,避免找不到的情况发生。
二、Class.forName源码分析
Class.forName(String className);这个方法的源码是
@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
Class.forName()调用forName0方法,forName0的第二个参数被默认设置为true,这个参数代表是否对加载的类进行初始化,设置为true表示进行类初始化,代表会执行类中的静态代码块,以及对静态常量的赋值等操作。
可以清楚的看到Class.forName()方法实际上是调用ClassLoader来实现的。
ClassLoader是遵循双亲委派模型,调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。
三、为什么推荐Class.forName()获取class?
Class.forName(),我相信我们第一次接触它的时候,大多数是在学习JDBC的时候。
使用JDBC时通常使用Class.forName()方法来加载数据库连接驱动。这是因为在JDBC规范中明确要求Driver(数据库驱动)类必须向DriverManager注册自己。
以MySQL的驱动为例解释:
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!");
}
}
...
}
通过源码可以看出Driver注册到DriverManager中的操作写在了静态代码块中,这就是为什么在写JDBC时使用Class.forName()的原因。
Class.forName()在加载数据库驱动时,是一种破坏双亲委派机制的使用,通过Application ClassLoader来加载一个第三方类,并没有使用父级的Bootstrap ClassLoad加载器。
总之一句话,推荐使用Class.forName()方法获取Class的最重要原因就是Class.forName()方法做了类的静态初始化(Class.class方法没有进行类的静态初始化;对象.getClass()方法虽然也做了静态初始化,但还需要new一个对象出来,不是很方便)。
参考:
简单谈谈你对 Java 中 Class.forName()、Class.class、getClass() 三者的理解?
在Java的反射中,Class.forName和ClassLoader的区别