前言
今天,在某个群里,一个群友问了一个问题。非常基础的一个问题,结果却不懂得如何解释,有种一知半解的感觉,于是就围绕问题,展开了思考。
题目
以下两段代码输出的结果是什么?为什么?
public class T extends A {
int x;
public T(){
super();
num=x;
}
public static void main(String[] args) {
System.out.println(new T().num);
}
@Override
protected void name() {
x=3;
}
}
abstract class A {
protected int num =-1;
public A(){
name();
}
protected abstract void name ();
}
复制代码public class T extends A {
int x=1;
public T(){
super();
num=x;
}
public static void main(String[] args) {
System.out.println(new T().num);
}
@Override
protected void name() {
x=3;
}
}
abstract class A {
protected int num =-1;
public A(){
name();
}
protected abstract void name ();
}
复制代码
分析
两段代码,分别输出的结果为3和1。然鹅,为什么呢?有人觉得name()没有执行?
因为T继承了A,所以正常情况下,创建一个新的T对象的同时,应该首先执行父类A的构造函数,再执行子类T的构造函数,所以第一种情况,代码输出为3是正常的。可是为什么x有初始值的情况下,代码输出为1呢?
分别将两段代码进行javap -c 反编译之后,发现两种情况下的差异在于执行构造函数时,多了一次赋值操作。
如图,左侧为x未赋值情况下反编译后的输出,右侧为x=1情况下反编译后的输出。为了方便查看,在此通过Jad进行反编译,来更加直观的表示结果。
可以看出,代码首先执行了A(),此时x应该为3。当x变量仅声明而没赋值的时候(左侧),程序直接进行了num的赋值。当x变量声明并赋了初值的时候(右侧),程序重新执行了赋初值的操作,导致x=1,最后得到的结果num=1。
扩散思考
这种情况是特例吗?明显不是!可能用高级语言用久了,有人疏忽了,变量其实是有声明和定义两步的。很明显,Java在进行编译的时候,是将这分开执行,而变量的定义则是在构造函数中执行,并且迟于父类构造函数执行,早于任意赋值语句执行。
为了更好理解,在此增加多另一个实例,供扩展使用
public class T extends A {
int x=1;
int y=1;
Test test=new Test();
public T(){
super();
num=x;
}
public T(int i){
super();
this.x=i;
num=x;
}
public static void main(String[] args) {
System.out.println(new T().num);
}
@Override
protected void name() {
x=3;
}
}
abstract class A {
protected int num =-1;
public A(){
name();
}
protected abstract void name ();
}
复制代码
无论后期如何操作,变量会在构造函数内部完成定义,再实现新的赋值操作。
总结
Java中变量的定义则是在构造函数中执行,并且迟于父类构造函数执行,早于任意赋值语句执行