image.png
其中,加载,验证,准备,初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序开始,而类的解析不一定,类的解析可能在初始化阶段之后再开始,这是为了支持Java语言的动态绑定
在Java中,当你调用一个方法时,可能会在编译时期(compile time)解析(resolve),也可能实在运行时期(runtime)解析,这全取决于到底是一个静态方法(static method)还是一个虚方法(virtual method)。如果是在编译时期解析,那么就称之为静态绑定(static binding),如果方法的调用是在运行时期解析,那就是动态绑定(dynamic binding)或者延迟绑定(late binding)。Java是一门面向对象的编程语言,优势就在于支持多态(Polymorphism)。多态使得父类型的引用变量可以引用子类型的对象。如果调用子类型对象的一个虚方法(非private,final or static),编译器将无法找到真正需要调用的方法,因为它可能是定义在父类型中的方法,也可能是在子类型中被重写(override)的方法,这种情形,只能在运行时进行解析,因为只有在运行时期,才能明确具体的对象到底是什么。这也是我们俗称的运行时或动态绑定(runtime or dynamic binding)。另一方面,private static和final方法将在编译时解析,因为编译器知道它们不能被重写,所有可能的方法都被定义在了一个类中,这些方法只能通过此类的引用变量进行调用。这叫做静态绑定或编译时绑定(static or compile time binding)。所有的private,static和final方法都通过静态绑定进行解析。这两个概念的关系,与“方法重载”(overloading,静态绑定)和“方法重写”(overriding,动态绑定)类似。动态绑定只有在重写可能存在时才会用到,而重载的方法在编译时期即可确定(这是因为它们总是定义在同一个类里面)
总而言之,其区别如下:
①静态绑定在编译时期,动态绑定在运行时期。
②静态绑定只用到类型信息,方法的解析根据引用变量的类型决定,而动态绑定则根据实际引用的的对象决定
③在java中,private static 和 final 方法都是静态绑定,只有虚方法才是动态绑定
④多态是通过动态绑定实现的。
public class SubClass extends SuperClass {
static {
System.out.println("I am sub class!");
}
}
public class SuperClass {
public static int value = 9;
static {
System.out.println("I am super class!");
}
}
public class DemoMain {
public static void main(String[] args) {
System.out.println("test:" + SubClass.value);
}
}
SuperClass中定义了一个static字段value,在程序运行的时候通过子类区调用value,这时候只会对SuperClass进行初始化,而不会对SubClass初始化,运行结果如下:
I am super class!
test:9
所以对于静态字段的引用,虽然是SubClass.value,但是由于value是static的,并且是在SuperClass中声明的,所以只会初始化SuperClass,当使用SubClass.value来访问value的时候,这时候是跟SubClass无关的,父类中的static字段或者static方法是可以被子类覆盖的,如果子类中有相同的声明,那么对于这个子类就会默认覆盖掉父类中对应的声明,如果子类中没有相同的声明,那么就直接继承自父类中的相关方法或者变量,但是static方法不具有多态,因为多态是对于一个对象来说才有多种形态,对于类来说没有多态的概念,而static方法就是相对于类而言的,因为static方法不具备多态特性,举个例子:
A是父类,B继承自A
A b = new B();
b.callMethod();
这就是b对象的多态,A中声明了callMethod方法,B中也可能也声明了callMethod方法,那么在b对象调用callMethod方法的时候执行的是B对象中重写的callMethod方法,这是在运行期间才知道callMethod方法要调用的是B的方法,多态就是Java中的动态绑定,在运行期间才能确定具体调用的是哪一个方法
在类的加载过程中,数组的加载不需要类加载器来加载,而是java虚拟机直接创建的,但是数据中的元素如果还是一个类对象,那么这个元素对应的类也需要类加载器来加载;在生成Class对象的时候需要注意,Class对象其实是一个对象,但是它是存在方法区中的,正常情况下Class对象的引用存在JVM栈中,而对象实际分配的内存空间是在堆内存中
try {
// ...
} catch (ReflectiveOperationException e) {
}
但是如果修改成下面这种,在程序不运行到这里的时候不会崩溃,一切正常,一旦程序运行到这里,那么就会报NoClassDefFoundError异常:
System.out.println("" + ReflectiveOperationException.class);
这个说明在程序运行到这里的时候JVM才去加载ReflectiveOperationException这个类,但是加载失败,而前面一个是程序一启动运行的时候,JVM会去加载引用ReflectiveOperationException的这个类,并且加载成功了,然后就会对引用了ReflectiveOperationException的这个类进行验证,说明JVM在解析try catch语句的时候是有所不同的
类验证主要做了以下几种验证:
public static final int value = 9;
那么在类准备阶段后,由于value变量被static修饰,那么value的初始值会为0,所以value就可以被其他类访问,当类初始化之后,执行了<clinit> ()方法,value的值才是9