首先我们来看一道面试题:
代码如下,a和b是在什么时候赋值的?
package com.demo;
public class Test {
private int a = 11;
private static int b = 22;
protected void testMethod() {
}
}
要回答这个题目,我们需要线了解init和cinit方法。
使用javap -verbose class文件可以得到如下字节码信息,可以看到红框里面分别是init和cinit方法。
其中<clinit>方法在javap命令中没有显示,javap输出的static{}就是<clinit>方法。
java中的构造器有两种:
实例构造器<init>和类构造器<clinit>
构造器的作用:
构造器的产生过程实际上是一个代码收敛的过程,编译器会把语句块(对于实例构造器而言是“{}”块,对于类构造器而言是“static{}”块)、变量初始化(实例变量和类变量)、调用父类的实例构造器(仅仅是实例构造器,<clinit>()方法中无须调用父类的<clinit>()方法,虚拟机会自动保证父类构造器的执行,但在<clinit>()方法中经常会生成调用java.lang.Object的<init>()方法的代码)等操作收敛到<init>()和<clinit>()方法之中,并且保证一定是按先执行父类的实例构造器,然后初始化变量,最后执行语句块的顺序进行.
<init> 和 <clinit> 区别:
init 针对的是实例, cinit针对是类, 数量上来来讲init构造器至少存在一个. cinit构造器只存在一个. 因为类对象在jvm内存中只会存在一个(同一个类加载器)
实例<init>构造器与构造函数的区别与联系:
构造器是由javac 命令生成.而构造函数则实质为我们写的java代码 构造方法(也叫构造函数), <init>构造器的生成和构造函数相互对应,如果我们的java代码没有显示的构造函数,那编译器将会添加一个没有参数的、访问性(public、protected或private)与当前类一致的默认构造函数.并根据默认构造函数生成<init>.
实例代码块"{}" 优先级高于构造函数中的赋值动作.多个"{}"按先后顺序最终合并到<init>中.
然后来看第一个红框内的bipush虚拟机指令,把11压入操作栈,然后putfield,赋值给变量a。
第二个红框同理,到这答案已经明了了:a和b分别是在init和cinit的方法中赋值的。
下面用一个例子check一下父类和子类的顺序
Parent.java
package com.demo;
public class Parent {
private String fatherA;
private static String fatherStaticB;
public Parent(){
System.out.println("Parent()");
System.out.println("========Parent构造函数========");
System.out.println();
}
{
System.out.println("========parent实体构造器start========");
System.out.println("========parent实体构造器end========\n");
}
static {
System.out.println("========parent类构造器start========");
System.out.println("========parent类构造器end========\n");
}
}
Sub.java
package com.demo;
public class Sub extends Parent{
private String subA;
private static String subStaticB;
Sub(){
System.out.println("Demo()");
System.out.println("========sub构造函数========");
}
{
System.out.println("========sub实体构造器start========");
System.out.println("========sub实体构造器end========\n");
}
static {
System.out.println("========sub类构造器start========");
System.out.println("========sub类构造器end========\n");
}
public static void main(String[] args) {
Sub a = new Sub();
}
}
运行结果:
可以判断得出
<clinit>
方法是在类加载过程中执行的,而<init>
是在对象实例化执行的,所以<clinit>
一定比<init>
先执行。所以整个顺序就是:
1. 父类静态变量初始化、静态语句块(经验证:按代码先后顺序执行)
2. 子类静态变量初始化、静态语句块(先后顺序执行)
3. 父类变量初始化、普通语句块(先后顺序执行)
4. 父类构造函数
5. 子类变量初始化、普通语句块(先后顺序执行)
6. 子类构造函数