<init>构造器和<cinit>以及构造方法的关系

首先我们来看一道面试题:

代码如下,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. 子类构造函数

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值