構(gòu)造方法的初始化順序(翻譯:Cherami)
發(fā)表時(shí)間:2024-02-22 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]構(gòu)造方法的初始化順序翻譯:Cheramiemail:cherami@163.net原文: http://java.sun.com/jdc/TechTips/2000/tt1205.html 想像一下你正在用java寫程序,并且用下面的代碼初始化類 A 和 B 的對象:class A int a =...
構(gòu)造方法的初始化順序
翻譯:Cherami
email:cherami@163.net
原文: http://java.sun.com/jdc/TechTips/2000/tt1205.html
想像一下你正在用java寫程序,并且用下面的代碼初始化類 A 和 B 的對象:
class A {
int a = f();
int f() {
return 1;
}
}
class B extends A {
int b = a;
int f() {
return 2;
}
}
public class CtorDemo1 {
public static void main(String args[]) {
B bobj = new B();
System.out.println(bobj.b);
}
}
現(xiàn)在,好像很明顯的當(dāng)初始化完成后,bobj.b的值將是1。畢竟,類B中的b 的值是用類A中的a的值初始化的,而a 是用f 的值初始化的,而它的值為1,對嗎?
實(shí)際上, bobj.b 的值是2,要知道為什么需要知道對象初始化的問題。
當(dāng)一個(gè)對象被創(chuàng)建時(shí),初始化是以下面的順序完成的:
1. 設(shè)置成員的值為缺省的初始值 (0, false, null)
2. 調(diào)用對象的構(gòu)造方法 (但是還沒有執(zhí)行構(gòu)造方法體)
3. 調(diào)用父類的構(gòu)造方法
4. 使用初始化程序和初始塊初始化成員
5. 執(zhí)行構(gòu)造方法體
看看在實(shí)際中是如何一步一步完成的,看看下面的例子:
class A {
A() {
System.out.println("A.A called");
}
}
class B extends A {
int i = f();
int j;
{
j = 37;
System.out.println("initialization block executed");
}
B() {
System.out.println("B.B called");
}
int f() {
System.out.println("B.f called");
return 47;
}
}
public class CtorDemo2 {
public static void main(String args[]) {
B bobj = new B();
}
}
程序的輸出是:
A.A called
B.f called
initialization block executed
B.B called
B 的構(gòu)造方法被調(diào)用,但是最先做的事情是隱含的調(diào)用父類的構(gòu)造方法。父類必須自己負(fù)責(zé)初始化它自己的狀態(tài)而不是讓子類來做。
然后B對象的成員被初始化,這包含一個(gè)對B.f 的調(diào)用和包圍在{}中的初始塊的執(zhí)行。最后B的構(gòu)造方法體被執(zhí)行。
你可能會(huì)問“什么是對父類的構(gòu)造方法的隱含調(diào)用”。這意味著如果你的構(gòu)造方法的第一行不是下面內(nèi)容之一:
super();
super(args);
this();
this(args);
則有下面的調(diào)用:
super();
提供給構(gòu)造方法的第一行。
如果類沒有構(gòu)造方法呢?在這種情況下,一個(gè)缺省的構(gòu)造方法(也叫"無參構(gòu)造方法")由java編譯器自動(dòng)生成。缺省構(gòu)造方法只有在類沒有任何其它的構(gòu)造方法時(shí)才產(chǎn)生。
更深入的明白這個(gè),假設(shè)在文件A.java中有這樣的代碼:
public class A {
public static void main(String args[]) {
A aref = new A();
}
}
如果你想編譯然后列出A.class 中的字節(jié)碼,輸入下面的內(nèi)容:
$ javac A.java
$ javap -c -classpath . A
輸出:
Compiled from A.java
public class A extends java.lang.Object {
public A();
public static void main(java.lang.String[]);
}
Method A()
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 return
Method void main(java.lang.String[])
0 new #2 <Class A>
3 dup
4 invokespecial #3 <Method A()>
7 astore_1
8 return
在main 中,注意對 A 的構(gòu)造方法的調(diào)用(就是invokespecial 行),以及A的構(gòu)造方法中產(chǎn)生的類似的對Object 構(gòu)造方法的調(diào)用。
如果父類沒有缺省構(gòu)造方法,你必須明確使用"super(args)"調(diào)用父類的某個(gè)構(gòu)造方法,例如,下面是一個(gè)錯(cuò)誤的用法:
class A {
A(int i) {}
}
class B extends A {}
在上面的情況下, A 沒有缺省的構(gòu)造方法,但是B的構(gòu)造方法必須調(diào)用A的某個(gè)構(gòu)造方法。
讓我們來看看初始化的另一個(gè)例子:
class A {
A() {
System.out.println("A.A called");
}
A(int i) {
this();
System.out.println("A.A(int) called");
}
}
class B extends A {
int i = f();
int j;
{
j = 37;
System.out.println("initialization block executed");
}
B() {
this(10);
System.out.println("B.B() called");
}
B(int i) {
super(i);
System.out.println("B.B(int) called");
}
int f() {
System.out.println("B.f called");
return 47;
}
}
public class CtorDemo3 {
public static void main(String args[]) {
B bobj = new B();
}
}
程序的輸出是:
A.A called
A.A(int) called
B.f called
initialization block executed
B.B(int) called
B.B() called
這個(gè)例子明確使用super() 和 this() 調(diào)用。this()調(diào)用是調(diào)用同一個(gè)類中的另一個(gè)構(gòu)造方法;這個(gè)方法被稱為“顯式構(gòu)造方法調(diào)用”。當(dāng)那樣的構(gòu)造方法被調(diào)用,它將執(zhí)行通常的super() 過程以及后續(xù)的操作。這意味著A.A 的方法體在A.A(int)之前執(zhí)行,而這兩個(gè)都在B.B(int) 和B.B 前執(zhí)行。
如果返回第一個(gè)例子,你就可以回答為什么打印的是2而不是1。B 沒有構(gòu)造方法,因此生成一個(gè)缺省構(gòu)造方法,然后它調(diào)用super(),然后調(diào)用A 產(chǎn)生的缺省構(gòu)造方法。
然后A中的成員被初始化,成員a 被設(shè)置為方法f()的值,但是因?yàn)锽 對象正被初始化,f() 返回值2。換句話說,調(diào)用的是B中的f()方法。
A產(chǎn)生的構(gòu)造方法體被執(zhí)行,然后B的成員被初始化,而b 被賦予值a,也就是2。最后,B的構(gòu)造方法被執(zhí)行。
最后一個(gè)例子說明了第一個(gè)例子的一個(gè)小小的變異版本:
class A {
int a = f();
int f() {
return 1;
}
}
class B extends A {
int b = 37;
int f() {
return b;
}
}
public class CtorDemo4 {
public static void main(String args[]) {
B bobj = new B();
System.out.println(bobj.a);
System.out.println(bobj.f());
}
}
程序的輸出是:
0
37
你可能會(huì)期望輸出的兩個(gè)值bobj.a 和bobj.f()是一樣的,但是正如你看到的他們不一樣。這是正確的,即使是在a是從B的f方法中初始化的并且打印的是a 和 B的 f 方法的值。
這兒的問題是當(dāng)a通過對B的f方法調(diào)用而初始化,而該方法返回成員b的值,而該成員還沒有被初始化。因?yàn)檫@個(gè),b的值就是剛開始的初始值0。
這些例子解釋了編程中重要的一點(diǎn)――在對象的構(gòu)造階段調(diào)用可重載的方法是不明智的。