[英]How is a child object constructed in Java?
在java中,子對象是如何構造的? 我剛開始繼承,這幾點對我來說不是很清楚:
子對象是只依賴於子類的構造函數,還是也依賴於父類的構造函數? 我需要關於這一點的一些細節。
另外, super() 是否總是在子構造函數中默認調用?
感謝有關此主題的任何其他信息。
我不認為“A child object”是思考這個問題的好方法。
你正在制作一個對象。 像所有對象一樣,它是某個特定類的實例,(畢竟, new SomeInterface()
不編譯)並且像(幾乎)所有對象一樣,它是因為某個地方的某些代碼(不必是您的代碼,當然)運行 java 表達式new SomeSpecificClass(args);
某處。
我們可以說它是一個“子對象”,因為SomeSpecificClass
是某個其他類的子類。
但這相當沒用。 這意味着創建新的“非子”對象的唯一方法是編寫new Object();
- 畢竟,除java.lang.Object
之外的所有類都是子類:如果您編寫public class Foo {}
,java 將解釋它,就像您編寫public class Foo extends java.lang.Object {}
,畢竟。
所以,除非無用*不相關,所有對象都是子對象,因此作為一個術語,“子對象”,我不會使用它。
這也意味着所有對象的創建都經過這個“好的,以什么順序以及構造函數如何工作”的歌曲和舞蹈例程。
它的工作原理可能最容易通過將其全部脫糖來解釋。 如果您選擇省略它們,Javac(編譯器)會注入一些東西,因為在類文件/JVM 級別,很多感覺是可選的(例如構造函數、超級調用或擴展子句)並不是* *.
已經涵蓋:如果您的類 def 上沒有extends
子句,javac 會為您注入extends java.lang.Object
。
構造函數必須在其第一行調用某個特定的超級構造函數,或者,它必須在其第一行( this(arg1, arg2);
)調用來自同一類的其他構造函數。 如果你不這樣做,java 會為你注入它:
public MyClass(String arg) { this.arg = arg; }
// is treated as:
public MyClass(String arg) {
super();
this.arg = arg;
}
值得注意的是,如果您的父類沒有可用的零參數構造函數,則包括編譯器錯誤。
如果您編寫一個沒有構造函數的類,那么 java 會為您創建一個:
public YourClass() {}
它將是公開的,沒有參數,也沒有代碼。 但是,根據糖 #2 規則,這會進一步擴展,以:
public YourClass() {super();}
當您創建新對象時,構造函數並不是唯一運行的東西。 想象一下這段代碼:
public class Example {
private final long now = System.currentTimeMillis();
}
此代碼有效; 你可以編譯它。 您可以創建Example
新實例,並且now
字段將保存您調用new Example()
時的時間。 那么它是如何工作的呢? 這感覺很像構造函數代碼,不是嗎?
好吧,它是這樣工作的:從上到下瀏覽源文件,找到你能找到的每一個非靜態初始化代碼:
public class Example {
int x = foo(); // all non-constant initial values count
{
x = 10;
// this bizarre constructor is legal java, and also
// counts as an initializer.
}
}
然后按照您看到的順序將所有內容移到類獲得的唯一一個初始化程序中。
因此,通過糖規則,我們減少了所有類以遵守以下規則:
現在唯一的問題是,執行的順序是什么?
答案很瘋狂。 抓住你的帽子。
這是順序:
首先,將整個“構造”的所有字段設置為 0/false/null(當然,構造涉及從 Child 一直到 Object 的每個字段)。
從在Child
調用的實際構造函數開始。 直接運行它,這意味着,與第一線,這neccessarily或者是啟動this()
或super()
調用。
評估整行,特別是評估作為參數傳遞的所有表達式。 即使這些本身就是對其他方法的調用。 但是,javac的會做一些小的努力,試圖阻止你訪問你的域(因為這些都是未初始化!我沒有提到的初始化尚未!!)。
是的,真的。 這意味着:
public class Example {
private final long x = System.currentTimeMillis();
public Example() {
super(x); // x will be .... 0
// how's that for 'final'?
}
}
這將最終調用您的其他一些構造函數的第一行(它本身也是this()
或super()
調用)。 要么我們永遠不會離開這個森林並且堆棧溢出錯誤中止了我們創建這個對象的嘗試(因為我們有一個無休止地相互調用的構造函數循環),或者,在某些時候,我們遇到了一個super()
調用,這意味着我們現在去我們的父類並再次重復整個歌曲和舞蹈程序。
我們繼續前進,一直到java.lang.Object
,通過硬編碼,它根本沒有this()
或super()
調用,並且是唯一一個調用。
那么,我們先停下來。 現在的工作是在 jlObject 的構造函數中運行其余的代碼,但首先,我們運行 Object 的初始化程序。
然后,對象的構造函數運行其中的所有其余代碼。
然后,運行 Parent 的初始化程序。 然后是使用的父構造函數的其余部分。 並且如果 parent 一直在橫向移動( this()
在其構造函數中調用),那么它們都將在方法調用中以相反的順序運行。
我們終於在 Child 結束了; 它的初始化程序運行,然后構造函數按順序運行,最后我們完成了。
class Parent {
/* some utility methods so we can run this stuff */
static int print(String in) {
System.out.println("@" + in);
return 0;
// we use this to observe the flow.
// as this is a static method it has no bearing on constructor calls.
}
public static void main(String[] args) {
new Child(1, 2);
}
/* actual relevant code follows */
Parent(int arg) {
print("Parent-ctr");
print("the result of getNow: " + getNow());
}
int y = print("Parent-init");
long getNow() { return 10; }
}
class Child extends Parent {
Child(int a, int b) {
this(print("Child-ctr1-firstline"));
print("Child-ctr1-secondline");
}
int x = print("Child-init");
Child(int a) {
super(print("Child-ctr2-firstline"));
print("Child-ctr2-secondline");
}
final long now = System.currentTimeMillis();
@Override long getNow() { return now; }
}
現在是偉大的益智游戲。 應用上述規則並嘗試弄清楚這將打印什么。
@Child-ctr1-firstline
@Child-ctr2-firstline
@Parent-init
@Parent-ctr
@getNow 的結果:0
@Child-init
@Child-ctr2-secondline
@Child-ctr1-secondline
——
*) 您可以將它們用於鎖或哨兵指針值。 讓我們說“基本上沒用”。
**) 你可以破解一個類文件,以便它描述一個沒有父類的類(甚至不是 jlObject); 這就是java.lang.Object
的類文件的工作方式。 但是你不能讓javac
做到這一點,你必須一起破解它,這樣的事情會非常瘋狂並且沒有真正有用的目的。
母親
public class Mother {
int a;
public Mother() {
System.out.println("Mother without argument");
a = 1;
}
public Mother(int a) {
System.out.println("Mother with argument");
this.a = a;
}
}
孩子
public class Child extends Mother {
public Child() {
System.out.println("Child without argument");
}
public Child(int a) {
super(a);
System.out.println("Child with argument");
}
}
如果你這樣做:
Child c1 = new Child();
你會得到 :
Mother without argument
Child without argument
如果你這樣做:
Child c1 = new Child(a);
你會得到 :
Mother with argument
Child with argument
但是,如果您將第二個子構造函數更改為並刪除 super(arg),則將調用沒有參數的父構造函數:
public Child(int a) {
// super(a);
System.out.println("Child with argument");
}
你會得到 :
Mother without argument
Child with argument
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.