簡體   English   中英

Java中如何構造子對象?

[英]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 級別,很多感覺是可選的(例如構造函數、超級調用或擴展子句)並不是* *.

糖 #1 - 擴展條款

已經涵蓋:如果您的類 def 上沒有extends子句,javac 會為您注入extends java.lang.Object

糖 #2 - 構造函數中沒有超級調用

構造函數必須在其第一行調用某個特定的超級構造函數,或者,它必須在其第一行( this(arg1, arg2); )調用來自同一類的其他構造函數。 如果你不這樣做,java 會為你注入它:

public MyClass(String arg) { this.arg = arg; }
// is treated as:
public MyClass(String arg) {
    super();
    this.arg = arg;
}

值得注意的是,如果您的父類沒有可用的零參數構造函數,則包括編譯器錯誤。

糖#3:沒有構造函數

如果您編寫一個沒有構造函數的類,那么 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.
    }
}

然后按照您看到的順序將所有內容移到類獲得的唯一一個初始化程序中。

訂購

因此,通過糖規則,我們減少了所有類以遵守以下規則:

  1. 所有班級都有一個父班級。
  2. 所有類至少有 1 個構造函數。
  3. 所有構造函數都調用另一個構造函數或來自父級的構造函數。
  4. 有一個“初始化程序”代碼塊。

現在唯一的問題是,執行的順序是什么?

答案很瘋狂 抓住你的帽子。

這是順序:

首先,將整個“構造”的所有字段設置為 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

  • 構造函數執行順序是有效的:第一行先行,其余行最后。
  • 最后一個字段是 0,即使它看起來永遠不應該是 0。
  • 你總是最終運行你父母的構造函數。

——

*) 您可以將它們用於鎖或哨兵指針值。 讓我們說“基本上沒用”。

**) 你可以破解一個類文件,以便它描述一個沒有父類的類(甚至不是 jlObject); 這就是java.lang.Object的類文件的工作方式。 但是你不能讓javac做到這一點,你必須一起破解它,這樣的事情會非常瘋狂並且沒有真正有用的目的。

  • 在繼承中,子對象的構造至少依賴於一個父構造器。
  • 調用 super() 方法不是強制性的。 默認情況下,Java 將不帶參數調用父構造函數,除非您精確地定義了自定義構造函數。
  • 這里有一個例子

母親

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM