我们通常说的类初始化,其实要分为三个阶段,类加载,连接,和初始化。他们大致完成以下功能。类加载将class文件载入内存,类连接进行内存分配,初始化进行变量赋值。
类的加载,连接和初始化
java.lang.Class
来自JAVA API的解释
Instances of the class Class represent classes and interfaces in a running Java application. Class
has no public constructor. Instead Class
objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass
method in the class loader.
Class类的实例代表正在执行的Java程序的类和接口,Class类没有public的构造函数, 每当有类被载入,或者在call处于加载器中的类方法时,JVM就会自动去构造Class类。
个人理解为,如果说一个类是一种对象的抽象的话,那么一个java.lang.Class类就是所有类的抽象,即抽象的抽象,那么所有类(而不是对象)本身就是java.lang.Class的一个实例。JAVA API中的定义是Class Class<T> (返回值和类名称一样!)在JAVA API中可以看到java.lang.Class定义了很多重要的方法可以返回类的属性,这里说的属性指的是这个类本身的性质,而不是说类中定义了什么变量或者方法。常用的态方法有,
public static Class<?> forName(String className) ——得到”className“类的Class实例,并初始化类”className“
这是非常典型的用法,注意这里的"Class实例"相对于java.lang.Class是一个实例,但是相对于我们常说的类和对象,其实这依然还是个类,要使用这个类,还需要创建对象(跟直接使用这个类创建对象还是有区别的)。这就是上面”抽象的抽象“的理解。
public Method[] getMethods() ——返回所有类拥有的public方法(包括继承来的以及来自接口的)的Method数组,可以通过数组元素调用invoke来执行这些方法
public T newInstance()——创建类的实例
类的加载
类的加载指的是将类的class文件载入内存,并为之创建一个java.lang.Class对象。可以从下面几种来源加载类,
- 本地文件系统
- JAR包
- 网络
- 把一个java文件动态编译,并执行加载。
类加载完成后,JVM就为其生成一个java.lang.Class对象,通过这个对象就可以操作类。
类的连接
所谓的连接,就是将类的二进制数据合并到JRE中, 连接阶段将会为变量分配内存以及设置默认初始值。
类初始化
类的初始化其实主要就是对变量初始化。变量初始化分两种,一种是声明变量时候就指定的初始值,另一种是静态初始化块。JVM都将按他们定义的顺序初始化。
如果变量所在类的类还有父类,则需要先初始化父类的变量,直到Object类。
类初始化的时机
- 创建类的实例,包括通过new创建,通过反射创建,通过反序列化创建
- 调用类的静态方法
- 访问类或接口的变量(静态变量)
- 通过反射方式强制创建类或接口的java.lang.Class对象。 例如Class.forName("Person")
- 初始化子类,其所有父类将被初始化
- 使用java.exe命令运行一个主类。
另外有如下特例,
- 对于final修饰的static变量,如果在编译期间就能确定值的,那么即使有别的类使用这个变量,也不会初始化这个类。 例如static final String a = "abc", 编译期间就知道值。
- 反之编译期间不能确定final类(static)变量的值,就会初始化这个类。例如 static final String a = System.currentTimeMillis() + "";
- ClassLoader类的loadClass()只是加载类,不初始化类。