在OOP语言中,都会有构造方法这个概念,它的主要作用就是用于创建一个类的实例,并为这个实例的成员变量赋值。虽然它是一个好像很简单的东西,但是好像还有很多人对它的理解还不是很透彻,往往在面试中就会搞一些比较2B的题目来考大家。
写这个主题主要是因为在自己的代码中出现了问题,在XStream中,它在实例化的时候是不会调用类的默认方法的,后面自己去跟了一下XStream的源码才发现,它是用了Unsafe中的allocateInstance方法来实例化的,而这个方法是不会去调用构造方法的,导致了Map cache = new HashMap();这个cache成员变量没有被赋值,最后用的时候就出现了NullPointerExption。
我猜这XStream是肯定没有调用类的默认构造方法,不然不可能会null的,结果就跟其他两个同事讨论起构造方法的事来,他们都说成员变量的赋值都是在构造方法之前执行的,而我说成员变量的初始化是在构造方法里面执行的。在论证这个之前,我们先来看一个比较误导人的命题:“当一个类没有定义任何构造方法时,编译器会生成一个什么都不做的默认构造方法”,我拿这个问题问了以前的同事,他竟然说这是对的,这段话好像在很多教材里面都会出现。这句话里的一半是对的,至于哪里错了,我们就要用事实来说话了。
先上一段简单的代码:
public class Test {
int a = 100;
}
这段代码只有简单的三行代码,但是却已经可以证明上面那句话的问题了。首先这个类里面是没有任何构造方法的,为了证明上面那句话是有一半错误的,我们需要看编译器为我们生成了些什么东西。在这里我们需要用到javap命令,这个命令能为我们的class文件转成对应的字节码指令。
在这里我们看到,编译器确实帮我们生成了一个默认的构造方法,但是它是什么都不做的默认构造方法么?明显不是,生成好几行的字节码指令。那么,我们现在来看一下这些生成的字节码指令都做了些什么事,首先会将this压入栈中,接着调用父类的构造方法,最后为成员变量赋值。现在我们就可以得出两个结论了:1、编译器为我们生成的默认构造方法并不是什么都不做的”懒人“;2、类中的成员变量的赋值是放到构造方法中去的,而并不是在构造方法之前执行的。相当于生成了以下的代码:
public class Test {
int a;
Test()
{
super(); // 首先去调用父类的构造方法
a = 100; // 为成员变量赋值
}
}
再来看一下构造方法里面的代码和成员变量赋值的顺序,再来一段简单的代码:
public class Test {
int a = 100;
int b = 200;
Test() {
int e = 300;
int f = 400;
}
int c = 500;
int d = 600;
}
看了这段简单的代码后,大家先花几秒钟的时间来猜一下它们的执行顺序是怎么样的
国际惯例,我们用javap命令来看一下它生成的字节码指令(javap真是一个好用的东西啊,哈哈)
看了这图之后,相信大家都已经知道答案了吧。相当于生成了以下的代码:
public class Test {
int a;
int b;
Test() {
a = 100;
b = 200;
c = 500;
d = 600;
int e = 300;
int f = 400;
}
int c;
int d;
}
哈哈,我就不多费口舌了。希望这篇博客能帮到大家。