本文共 2028 字,大约阅读时间需要 6 分钟。
在Java反射中,反射的核心入口就是Class对象。获取Class对象的方式有三种,这些方法在不同的场景下会有不同的使用场景和表现。了解这些方法的特点,可以帮助我们在实际开发中做出更合适的选择。
Class.class:这是一种直接获取目标类的Class对象的方式。这种方式让JVM去使用类加载器将指定类加载到内存中(前提是类还未被加载),并返回Class对象。这种方式不会执行类的静态初始化代码,因为它只是单纯地获取Class对象。 Class.forName();:这是一种通过类名字符串来获取Class对象的方式。与Class.class不同的是,Class.forName()会执行类的静态初始化代码,也就是说,它会依次执行类中的静态变量赋值、静态方法调用等操作。这种方式通常用于需要类初始化的场景,比如 JDBC中的DriverManager注册驱动类。 对象.getClass();:这是一种通过已有的对象实例来获取其类的Class对象的方式。当使用对象.getClass()时,除了会返回对象所属的类的Class对象外,还会执行该类的静态初始化和非静态初始化代码。这种方式通常用于对某个特定对象实例的类进行操作时使用。
需要注意的是,三种方式在生成Class对象时都会检查内存中是否已经存在该类的Class对象。如果类已经被加载到内存中,只会返回缓存的Class对象,不会再次去类加载器那里获取。
此外,Class.forName()方法在加载类时还会进行异常处理,具体来说,它会通过Reflection.getCallerClass()获取调用Class.forName()的方法的类加载器,从而避免 ClassNotFoundException异常。而Class.class和对象.getClass()方法则不会有这种异常处理机制。
Class.forName(String className)方法的源码可以帮助我们更深入地理解它的实现机制。以下是方法的核心部分:
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,这意味着它会执行加载的类的静态初始化代码。静态初始化代码主要包括类变量的赋值和静态方法的调用等操作。通过观察源码可以发现,Class.forName()方法的行为和Class.class方法有很大的区别,尤其是在对类进行初始化方面。
此外,Class.forName()方法通过调用ClassLoader来实现类的加载。ClassLoader遵循双亲委派机制,即先由Bootstrap ClassLoader加载,如果找不到,再由扩展类加载器加载,最后再由应用程序的类加载器加载。双亲委派机制保证了类的加载安全性和一致性。
在实际开发中,Class.forName()方法因为其独特的特性而常常被推荐用作获取Class对象的标准方式。以下是一些主要原因:
1. **静态初始化的支持**:Class.forName()方法会自动执行加载的类的静态初始化代码。这对于那些需要在加载到内存前完成静态变量赋值和静态方法调用等操作的类非常有用。
2. **与JDBC规范的兼容性**:在JDBC开发中,数据库驱动类通常会通过Class.forName()的方式注册自己。例如,MySQL的Driver类在静态代码块中会尝试注册自己为DriverManager可用的驱动类。这种方式依赖于Class.forName()的静态初始化功能,确保驱动类能够被正确加载和注册。
3. **避免双亲委派的问题**:通过Class.forName(),我们可以指定使用特定的类加载器来加载类,而不是让JVM按照双亲委派机制去查找和加载类。这使得我们能够更灵活地管理类的加载过程,尤其是在第三方库或自定义类加载器场景下非常有用。
总的来说,Class.forName()在实际应用中因为其支持静态初始化和灵活的类加载特性,成为了获取Class对象的首选方式。虽然Class.class和对象.getClass()方法也有各自的用途,但在需要类初始化的场景下,Class.forName()的优势更加明显。
转载地址:http://lobx.baihongyu.com/