- RTTI含义:运行时类型信息
- Class对象和类加载器
Class对象:包含了与类有关的信息,每一个类都有一个Class对象
类加载器:类加载器是Java虚拟机(JVM)的一个子系统,所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创 建一个对类的静态引用时,就会加载这个类。因此证明了构造器也是类的静态方法,用new操作费创建类的新对象也会被当做对类的静态成员引用。类加载时,类加载器首先检查类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件。static初始化是在类加载时进行的。 - 获取类的Class对象的方式
(1)Class 对象名 = Class.forName(“包名.类名”); 需要检查异常(用try-catch包围或throws抛出异常),并会初始化类
(2)Class 对象名 = 类名.class; 无需检查异常,不会进行初始化,比类名.forName方式更高效
(3)Class 对象名 = 类名.getClass();
(4)通过ClassLoader加载,ClassLoader.getSystemClassLoader().loadClass(“包名.类名”); 没有对类进行初始化,只是把类加载到了虚拟机中 - 使用类时准备工作三个步骤
(1)加载:由类加载器执行,查找字节码(通常在classpath所指定的路径中查找),并从这些字节码中创建一个Class对象、
(2)链接:在链接阶段将验证类中的字节码,为静态域分配存储空间,如果必需的话,将解析这个类创建对其它类的所有引用。
(3)初始化:如果该类具有超类,则对其进行初始化,执行静态初始化器(如构造方法)和静态初始化块。
使用.class方式获取类的Class对象时。初始化操作被延迟到对静态方法(构造器也是隐式地是静态的)或非常数静态域(需要运行时才能确定的静态域)进行首次调用时才执行
import java.util.Random;
class Initable {
static final int staticFinal = 47;
static final int staicFinal2 = Test1.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}
class Initable2 {
static int staticFinal = 147;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3 {
static final int staticFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
public class Test1 {
public static Random rand = new Random(47);
public static void main(String[] args) {
Class initable = Initable.class;
System.out.println("----1----");
System.out.println(Initable.staticFinal);
System.out.println(Initable.staicFinal2);
System.out.println("----2----");
System.out.println(Initable2.staticFinal);
System.out.println("----3----");
try {
Class initable3 = Class.forName("test1.test1.Initable3");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(Initable3.staticFinal);
}
}
运行结果:
可以看出,初始化有效的实现了尽可能的“惰性”。仅使用.class获得对类的引用不会引发初始化。而用Class.forName()就立即进行了初始化。(由于源码中默认设置了是否初始化为true,所以会进行初始化操作)
- 泛化的Class引用
Class引用总是指向某个Class对象,可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包含该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
使用泛型语法,是通过允许你对Class引用所指的Class对象进行限定而实现的。通过使用泛型语法,可以让编译器强制执行额外的类型检查。
向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查。
public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = intClass;
intClass = double.class;
//genericIntClass = intClasss; 不能将double的Class引用赋值给使用了Integer泛型的Class引用;
}
}
- 加入继承关系的泛化Class引用
Class<Number> genericNumberClass = int.class;
虽然Numbe是Integer的父类,但是这行代码是错误的,因为Number类的Class对象不是Integer类的Class对象的父类,我的理解是这两个实质是Class类的两个不同对象,表示的是两个不同的类。
我们需要使用通配符“?”来表示泛型。“?”表示任何事物。Class<?>和Class是等价的,但前者编译器不会有警告信息。它配合extends或super关键字表示一个范围,分别是:
(1)Class<? extends 类名> //继承了指定类的类,即指定类型的子类。
(2)Class<? super 类名> //指定类型的父类
如果使用了泛型的Class对象,在调用newInstance()方法时,当泛型为指定的确切类型时,会返回该指定类型的对象,而不是Object类型;当泛型为一个范围时,如果上面说的<? super 类名>,返回的是Object类型,而不是确切的类型。我的理解是不确定是哪一个父类类型,可能是父类也可能是祖父类,有含糊性。
- cast()转型方法
cast()方法接受参数对象,并将其转型为Class引用的类型。与普通的类型转换相比,新的转型语法对于无法使用普通转型的情况显得非常有用,在编写泛型代码时,如果存储了Class引用,并希望以后通过这个引用来执行转型,这种情况就会时有发生。
class Building{}
class House extends Building{}
public class ClassCast {
public static void main(String[] args) {
Building build = new Building();
Class<House> houseType = House.class; //获取Class对象,用于确定需要转换成什么类型
House house = houseType.cast(build); //通过houseType的cast(Object b)方法将传入的build对象转换为Hose类型的对象
house = (House)build; //普通的转型方法
}
}
源码拓展:
- 调用 Class.forName(String className)方法获得Class对象时,会执行类初始化的原因。
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);
}
其中,forName0方法是调用了private static native Class<?> forName0(String name, boolean initialize,ClassLoader loader,Class<?> caller)本地方法,其中第二个参数即为设置是否对类进行初始化的参数。 当调用Class.forName方法时,源码中默认设置为true,所以加载类时会进行初始化。
当需要设置不初始化时,可以调用forName(String name, boolean initialize,ClassLoader loader)来设置参数,true表示执行初始化。
Spring框架中的IOC的实现使用的是ClassLoader(没有对类进行初始化,只是把类加载到了虚拟机中)。
而在我们使用JDBC时通常是使用Class.forName()方法来加载数据库连接驱动。这是因为在JDBC规范中明确要求Driver(数据库驱动)类必须向DriverManager注册自己。(Class.forName(“com.mysql.jdbc.Driver”))
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// ~ Static fields/initializers
// ---------------------------------------------
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
// ~ Constructors
// -----------------------------------------------------------
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
我们看到Driver注册到DriverManager中的操作写在了静态代码块中,这就是为什么在写JDBC时使用Class.forName()的原因了。