【Java面试题】对于构造方法的疑问?——<init><cinit>与类的初始化

一,疑问

从学习java至今,我一直对构造方法存在很多疑问,在此将我的疑问throw,你们可以catch到吗?

面试官 :你说下构造方法吧!

我 :((⊙o⊙)… ,构造方法有什么聊的,对象的new不是一直在用吗?) 构造方法是一种特殊的方法,它是一个与类同名且没有返回值类型的方法。对象的创建就是通过构造方法来完成,其功能主要是完成对象的初始化。当类实例化一个对象时会自动调用构造方法。构造方法和其他方法一样也可以重载。

面试官 :你说构造方法功能是完成对象的初始化,类默认的构造方法怎么完成对象的初始化,它里面有没有代码哦!!!

我:(是啊,默认的构造方法没有代码,有也是super,怎么初始化的)这个... 属性不是都有默认值得吗? int类型是0,引用类型是null....

面试官 :解释下 private int i = 100; 这个i的值也是 0吗? 如果不是 0,是100,什么时候给赋的值? 或者是构造方法赋的值吗? 如果不是构造方法赋的值,代码里能看到那条代码给赋的值吗?

我 :代码里的int i = 100;不是给赋了100吗,就是这条代码给赋的值。

面试官:额,方法的调用,执行估计你也应该清楚,没有特殊逻辑基本都是顺序执行,Student stu =new Student(); 调用了Student的无参构造方法,这个构造方法基本没什么代码,你也清楚,请问 private int i = 100; 这条代码什么时候执行,构造方法显然没有调用这个代码!对吧!

我 :(挠头,代码没有执行这条复制语句啊?)可能是Java的设计者在构造方法中隐式执行了属性的赋值语句....(我tm机智了,哈哈哈)

面试官 :正确与否先不说,你的说法可以解释通,还有个问题,我要在构造方法里面打印这个 i ,会不会存在赋值与打印并发的现象,或者我打印在前,赋值在后引起了错误?

我 : 我想规则应该是赋值永远在代码最前面,不会出现这个问题...(松了口气,应该可以了吧!)

面试官:根据你的逻辑,能否解释下 public static String stuClass = "码农大学-颈椎病康复班";

我 : 解释什么???(一头雾水)

面试官: 我的意思是,这stuClass是个静态属性,我可以直接通过类名调用,我并没有new Studnet(),没有调用构造方法,这个stuClass是怎么赋值的?你的构造方法隐式赋值理论貌似解释不通啊?

我: 这个...... (我的心在滴血.....)

我的部分疑问,通过面试中的博弈呈现出来(面试过程为自己思考中的博弈,并非真实发生的),您能否解释这些问题....

  • new Student() 的过程发生了什么?
  • 构造方法干了什么?
  • 属性在什么时候初始化(赋值)?
  • 静态属性在什么时候初始化(赋值)?
  • .............

二,分析

接下来我们通过代码分析。

2.1 静态变量的赋值与静态代码块的执行

父类相关代码

//父类
public class Parent {
	
	/**父类的静态属性*/
	public static String preson;
	public static String presonn = "人类";
	
	/**父类的属性*/
	protected String name;
	protected String namee = "码农中的吴秀波";
	
	protected int age;
	protected int agee = 18;
	
	/**
	 * 父类的初始化代码块
	 */
	{
		System.out.println("Parent init ...");
		System.out.println("Parent init age:" + age);
		System.out.println("Parent init name:"+ name);
		System.out.println("Parent init namee:"+ namee);
		namee = "码农中的吴彦祖";
	}
	
	/**
	 * 父类中的静态初始化代码块
	 */
	static{
		System.out.println("Parent cinit ...");
		System.out.println("Parent cinit preson:"+preson);
		System.out.println("Parent cinit presonn:" + presonn );
		presonn = "人";
	}
	/**
	 * 父类的无参构造方法
	 */
	public Parent() {
		// TODO Auto-generated constructor stub
		System.out.println("Parent constructor ...");
		System.out.println("Parent constructor namee:"+ namee);
	}

}

子类的相关代码

//子类
public class Student extends Parent{

	/**子类的静态属性*/
	public static String stuclass = "码农大学-颈椎病康复班";
	
	/**子类的属性*/
	private int stuNum = 10001;
	
	/**
	 * 子类的初始化代码块
	 */
	{
		System.out.println("Student init ...");
		System.out.println("Student init stuNum :"+ stuNum);
	}
	
	/**
	 * 子类的静态代码块
	 */
	static{
		System.out.println("Student cinit ...");
		System.out.println("Student cinit stuclass:"+ stuclass);
	}
	/**
	 * 子类无参构造方法
	 */
	public Student() {
		// TODO Auto-generated constructor stub
		System.out.println("Student constructor  ...");
	}
	
	public static void main(String[] args) {
		
	}
	
}

代码相对比较简单,如果只关心程序入口,即子类Student的main方法,运行这个main方法后,您能预计结果是什么吗?

//console 输出
Parent cinit ...
Parent cinit preson:null
Parent cinit presonn:人类
Student cinit ...
Student cinit stuclass:码农大学-颈椎病康复班

输出的结果和您预期的一样吗?

通过输出结果我们可知,一个空的main方法的运行,执行顺序为:

  • 父类静态变量的初始化(静态代码块可以输出静态变量的值);
  • 父类静态代码块的执行(静态代码块可以重新给静态变量赋值,暂不演示了);
  • 子类静态变量的初始化;
  • 子类静态代码块的执行;

疑问一 :main 方法中并未出现代码,main方法的执行为什么会有如上的输出???

2.2 变量的赋值、初始化代码的运行以及构造方法的运行

public static void main(String[] args) {
		Student stu = new Student();
	}

执行如上代码,您能预测出输出结果吗?

//console 输出
Parent cinit ...
Parent cinit preson:null
Parent cinit presonn:人类
Student cinit ...
Student cinit stuclass:码农大学-颈椎病康复班
--------------------疯哥线---------------------------
Parent init ...
Parent init age:0
Parent init name:null
Parent init namee:码农中的吴秀波
Parent constructor ...
Parent constructor namee:码农中的吴彦祖
Student init ...
Student init stuNum :10001
Student constructor  ...

和您预期的一样吗?

通过输出结果可知,mian 方法以及 Student stu = new Student();代码,执行了

  • 静态相关输出,同空的mian 方法
  • 父类变量的初始化
  • 父类的初始化代码块
  • 父类的构造方法
  • 子类变量的初始化
  • 子类的初始化代码块
  • 子类的构造方法

疑问二: Student stu = new Student(); 此语句按常理应该只执行父类和子类的构造方法,为什么会有如此的输出(执行流程)?

三,解释-结论-注意

3.1 疑问一的解释

Java程序语言 -> 编译器 -> .class -> JVM运行.class ;

Java编译器会把静态变量的初始化和静态代码块顺序在字节码中生成为<clinit>方法,此方法称之为类的构造器;

虚拟机运行字节码有如下步骤(想要深入了解可以学习JVM、字节码相关知识):加载->连接(验证、准备、解析)->初始化->使用->卸载

初始化(执行<clinit>方法):

  • 遇到new、getstatic、putstatic、invokestatic执行初始化操作
  • 使用java.lang.reflect包的方法对类进行调用时,如果类没有进行过初始化,则需要先触发其初始化
  • 当初始化一个类时,如果发现父类还没有进行过初始化过,则需要先触发其父类的初始化
  • 当JVM启动时,用户需要指定一个要执行的类(包含main()方法的那个类),虚拟机会先初始化这个类

如上的初始化(加粗)步骤,完美解释了 疑问一:main 方法中并未出现代码,main执行为什么如上的输出???

虚拟机启动,我们指定了student类作为启动类(main方法),虚拟机加载...初始化这个类,即运行字节码中的<clinit>方法,发现父类也没有初始化,触发其父类的初始化;

问题:您能举出不被初始化的例子吗???

通过 javap -verbose Student.class 命令查看类的字节码

字节码文件-类的构造方法

3.2 疑问二的解释

字节码文件-实例的初始化方法

通过main()的字节码可以看出,Student stu = new Student();转换成字节码指令显然不是一条(JVM是以一条条指令为单位运行的),指令invokespecial:调用实例初始化,父类初始化和私有方法(调用了<init>方法)。

<init>:会将变量初始化,初始化语句块,构造器方法等操作顺序封装到该方法中(父类先于子类运行);

这样就可以解释疑问二了,Student stu = new Student();虽然只有一条代码,但虚拟机运行的字节码指令可不单单一条指令;

3.3 总结

<clinit>方法是在类加载过程中执行的,而<init>是在对象实例化执行的,所以<clinit>一定比<init>先执行。即执行顺序为:

  • 父类的静态变量初始化
  • 父类的静态代码块
  • 子类的静态变量初始化
  • 子类的静态代码块
  • 父类的变量初始化
  • 父类的初始化代码块
  • 父类的构造方法
  • 子类的变量初始化
  • 子类的初始化代码块
  • 子类的构造方法
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值